Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

1use crate::call_match::{
2    CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_forwarding_call_argument,
3    cpp_literal_arg_type, cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6    CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7    cpp_sentinel_recovered_scope_for_node, enclosing_cpp_namespace_nodes,
8    is_recovered_exported_class_base_type_node, node_text, normalize_cpp_whitespace,
9    recovered_macro_return_type_node,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
13use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
14use crate::graph::hits::{
15    enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
16    push_declared_reference_hit, push_definition_hit, push_hit, push_recovered_definition_hit,
17    push_recursive_reference_hit, push_reference_hit_range, push_self_receiver_hit, push_type_hit,
18    push_type_hit_range, push_unproven_definition_hit, push_unproven_hit,
19    push_unproven_reference_hit_range,
20};
21use crate::graph::resolver::*;
22use crate::graph::syntax::{
23    function_macro_replacement_span, object_macro_replacement_type_references,
24    qualified_callable_value, receiver_is_self_like,
25};
26use crate::graph_support::CppSource;
27use brokk_bifrost_core::analyzer::fq_name::segment_interner;
28use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
29use brokk_bifrost_core::analyzer::query_token::QueryToken;
30use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_is_within;
31use brokk_bifrost_core::analyzer::tree_walk::{
32    ParentIndex, WalkControl, children_iter, first_named_child_of_kind, parent_if_field,
33    push_named_children_reversed, root_node, walk_named_tree_preorder,
34};
35use brokk_bifrost_core::analyzer::usages::common::same_node;
36use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
37use brokk_bifrost_core::analyzer::usages::local_inference::{
38    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
39};
40use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
41use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
42use brokk_bifrost_core::hash::{HashMap, HashSet};
43#[cfg(any(test, feature = "test-support"))]
44use std::cell::Cell;
45use std::cell::RefCell;
46use std::collections::BTreeSet;
47use std::sync::Arc;
48use std::time::Instant;
49use tree_sitter::Node;
50
51#[cfg(any(test, feature = "test-support"))]
52thread_local! {
53    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
54    static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
55}
56
57#[cfg(any(test, feature = "test-support"))]
58pub fn reset_type_reference_candidate_scan_count_for_test() {
59    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
60}
61
62#[cfg(any(test, feature = "test-support"))]
63pub fn type_reference_candidate_scan_count_for_test() -> usize {
64    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
65}
66
67pub struct ScanState<'a> {
68    pub max_usages: usize,
69    pub hits: &'a mut BTreeSet<UsageHit>,
70    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
71    pub raw_match_count: &'a mut usize,
72    pub limit_exceeded: &'a mut bool,
73}
74
75pub struct ScanCtx<'a> {
76    pub analyzer: CppGraphSource<'a>,
77    pub visibility: &'a VisibilityIndex<'a>,
78    pub file: &'a ProjectFile,
79    pub source: &'a str,
80    /// The parent of every node in the file being scanned, recorded by one
81    /// downward pass when the scan starts. `Node::parent` recovers a parent by
82    /// re-descending from the root, so an ancestor climb is quadratic in depth
83    /// and the scan climbs from more than a dozen call sites per visited node
84    /// (#1927, #3097).
85    pub ancestry: ParentIndex<'a>,
86    ordinary_type_imports: OrdinaryTypeImportCell,
87    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
88    class_ranges: Option<&'a ClassRangeIndex>,
89    pub line_starts: &'a [usize],
90    pub spec: &'a TargetSpec,
91    pub target_group: &'a HashSet<CodeUnit>,
92    pub has_proven_visible_type_target: bool,
93    uses_c_semantics: bool,
94    type_reference_component_names: HashSet<String>,
95    pub target_declaration_ranges: Vec<Range>,
96    target_macro_declaration_bytes: Vec<usize>,
97    pub bindings: LocalInferenceEngine<CppScanBinding>,
98    local_shadows: LocalInferenceEngine<()>,
99    using_enum_owners: ScopedUsingEnumOwners,
100    semantic_using_enum_owners: SemanticUsingEnumOwners,
101    needs_using_enum_member_resolution: bool,
102    pub hits: &'a mut BTreeSet<UsageHit>,
103    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
104    pub raw_match_count: &'a mut usize,
105    pub max_usages: usize,
106    pub external_hit_count: usize,
107    pub limit_exceeded: &'a mut bool,
108    pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
109    pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
110    lexical_scope_cache: LexicalScopeCache,
111    lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
112    member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
113    global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
114    receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
115}
116
117impl ScanCtx<'_> {
118    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
119        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
120    }
121}
122
123#[derive(Clone, Default)]
124pub struct EnclosingContext {
125    pub enclosing: Option<CodeUnit>,
126    pub owner: Option<CodeUnit>,
127}
128
129pub fn prepare_file(
130    cpp: &dyn CppSource,
131    token: QueryToken<'_>,
132    file: &ProjectFile,
133) -> Option<Arc<PreparedSyntaxTree>> {
134    cpp.prepared_syntax(token, file)
135}
136
137#[allow(clippy::too_many_arguments)]
138pub fn scan_prepared_file(
139    analyzer: &CppGraphSource<'_>,
140    visibility: &VisibilityIndex<'_>,
141    file: &ProjectFile,
142    prepared: &PreparedSyntaxTree,
143    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
144    class_ranges: Option<&ClassRangeIndex>,
145    spec: &TargetSpec,
146    target_group: &HashSet<CodeUnit>,
147    state: &mut ScanState<'_>,
148) {
149    if *state.limit_exceeded {
150        return;
151    }
152    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
153    let has_proven_visible_type_target = spec.kind == TargetKind::Type
154        && (target_group.iter().any(|target| {
155            same_logical_symbol(target, &spec.target)
156                && visibility.is_physically_visible(file, target)
157        }) || {
158            let candidates = visibility
159                .visible_identifier_candidates(file, spec.target.identifier())
160                .collect::<Vec<_>>();
161            visibility.c_tag_declaration_family_matches_target(
162                analyzer,
163                file,
164                &candidates,
165                &spec.target,
166            )
167        });
168    if spec.kind == TargetKind::Type
169        && !has_proven_visible_type_target
170        && visibility
171            .visible_identifier_candidates(file, spec.target.identifier())
172            .any(|candidate| {
173                candidate != &spec.target
174                    && !target_group.contains(candidate)
175                    && same_logical_symbol(candidate, &spec.target)
176                    && visibility.is_physically_visible(file, candidate)
177                    && !visibility.c_tag_declaration_family_matches_target(
178                        analyzer,
179                        file,
180                        std::slice::from_ref(&candidate),
181                        &spec.target,
182                    )
183            })
184    {
185        return;
186    }
187    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
188        analyzer.ranges(&spec.target)
189    } else {
190        Vec::new()
191    };
192    let target_declaration_ranges = if spec.kind == TargetKind::Type {
193        target_group
194            .iter()
195            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
196            .flat_map(|target| analyzer.ranges(target))
197            .collect()
198    } else if spec.target.source() == file {
199        if spec.kind == TargetKind::Macro {
200            macro_target_declaration_ranges.clone()
201        } else {
202            analyzer.ranges(&spec.target)
203        }
204    } else {
205        Vec::new()
206    };
207    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
208        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
209    } else {
210        Vec::new()
211    };
212    let type_reference_component_names = if spec.kind == TargetKind::Type {
213        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
214    } else {
215        HashSet::default()
216    };
217    if spec.kind == TargetKind::Type
218        && !file_may_reference_type_target(
219            prepared.tree().root_node(),
220            prepared.source(),
221            analyzer,
222            visibility,
223            file,
224            &spec.target,
225            &type_reference_component_names,
226        )
227    {
228        return;
229    }
230    let ordinary_type_imports = initialized_ordinary_type_imports(
231        prepared.tree().root_node(),
232        analyzer,
233        visibility,
234        file,
235        prepared.source(),
236    );
237    let external_hit_count = state
238        .hits
239        .iter()
240        .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
241        .count();
242    let mut ctx = ScanCtx {
243        analyzer: *analyzer,
244        visibility,
245        file,
246        source: prepared.source(),
247        ancestry: ParentIndex::new(prepared.tree().root_node()),
248        ordinary_type_imports,
249        recovered_sentinel_classes,
250        class_ranges,
251        line_starts: prepared.line_starts(),
252        spec,
253        target_group,
254        has_proven_visible_type_target,
255        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
256        type_reference_component_names,
257        target_declaration_ranges,
258        target_macro_declaration_bytes,
259        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
260        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
261        using_enum_owners: ScopedUsingEnumOwners::new(),
262        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
263        needs_using_enum_member_resolution,
264        hits: state.hits,
265        unproven_hits: state.unproven_hits,
266        raw_match_count: state.raw_match_count,
267        max_usages: state.max_usages,
268        external_hit_count,
269        limit_exceeded: state.limit_exceeded,
270        enclosing_cache: RefCell::new(HashMap::default()),
271        enclosing_owner_cache: RefCell::new(HashMap::default()),
272        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
273        lexical_free_function_cache: RefCell::new(HashMap::default()),
274        member_owner_cache: RefCell::new(HashMap::default()),
275        global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
276        receiver_canonical_type_cache: RefCell::new(HashMap::default()),
277    };
278    if needs_using_enum_member_resolution {
279        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
280    }
281    if spec.kind == TargetKind::Macro {
282        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
283        return;
284    }
285    scan_node(prepared.tree().root_node(), &mut ctx);
286}
287
288/// Scan a macro target without maintaining the declaration and lexical state
289/// required by ordinary C and C++ symbols. Macro activation comes entirely
290/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
291/// node makes generated tables needlessly expensive.
292fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
293    walk_named_tree_preorder(root, true, |node| {
294        if *ctx.limit_exceeded {
295            return WalkControl::Break;
296        }
297        maybe_record_macro_hit(node, ctx);
298        WalkControl::Continue
299    });
300}
301
302enum UsingEnumDeclarationScope {
303    Block,
304    Class(CodeUnit),
305    Namespace(Vec<String>),
306    UnsupportedClass,
307}
308
309fn resolved_using_enum_declaration(
310    node: Node<'_>,
311    ctx: &ScanCtx<'_>,
312) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
313    let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
314        node,
315        &ctx.analyzer,
316        ctx.visibility,
317        &ctx.ordinary_type_imports,
318        ctx.file,
319        ctx.source,
320    ) else {
321        return None;
322    };
323    Some((unit, using_enum_declaration_scope(node, ctx)))
324}
325
326fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
327    let mut current = ctx.ancestry.parent(node);
328    while let Some(parent) = current {
329        if matches!(
330            parent.kind(),
331            "compound_statement"
332                | "function_definition"
333                | "lambda_expression"
334                | "for_statement"
335                | "while_statement"
336                | "if_statement"
337        ) {
338            return UsingEnumDeclarationScope::Block;
339        }
340        if matches!(
341            parent.kind(),
342            "class_specifier" | "struct_specifier" | "union_specifier"
343        ) {
344            let resolution = enclosing_lexical_scope_components(
345                node,
346                &ctx.analyzer,
347                ctx.visibility,
348                ctx.file,
349                ctx.source,
350            );
351            if let LexicalScopeResolution::Resolved(components) = resolution
352                && let LexicalTypeResolution::Resolved { unit, .. } =
353                    ctx.visibility.resolve_type_components_lexically(
354                        &ctx.analyzer,
355                        ctx.file,
356                        &components,
357                        true,
358                        &[],
359                    )
360            {
361                return UsingEnumDeclarationScope::Class(unit);
362            }
363            return UsingEnumDeclarationScope::UnsupportedClass;
364        }
365        current = ctx.ancestry.parent(parent);
366    }
367    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
368}
369
370fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
371    let mut stack = vec![root];
372    while let Some(node) = stack.pop() {
373        if node.kind() == "using_declaration"
374            && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
375        {
376            match scope {
377                UsingEnumDeclarationScope::Block => {}
378                UsingEnumDeclarationScope::Class(class) => {
379                    ctx.semantic_using_enum_owners.import_class(class, unit);
380                }
381                UsingEnumDeclarationScope::Namespace(namespace) => {
382                    ctx.semantic_using_enum_owners.import_namespace(
383                        namespace,
384                        node.start_byte(),
385                        unit,
386                    );
387                }
388                UsingEnumDeclarationScope::UnsupportedClass => {}
389            }
390        }
391        push_named_children_reversed(node, &mut stack);
392    }
393}
394
395fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
396    if *ctx.limit_exceeded {
397        return;
398    }
399    let enters_scope = matches!(
400        node.kind(),
401        "compound_statement"
402            | "function_definition"
403            | "lambda_expression"
404            | "for_statement"
405            | "for_range_loop"
406            | "while_statement"
407            | "if_statement"
408    );
409    let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
410        && (enters_scope
411            || matches!(
412                node.kind(),
413                "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
414            ));
415    if enters_scope {
416        ctx.bindings.enter_scope();
417        ctx.local_shadows.enter_scope();
418    }
419    if enters_using_enum_scope {
420        ctx.using_enum_owners.enter_scope();
421    }
422
423    seed_declarations(node, ctx);
424    maybe_record_hit(node, ctx);
425
426    let translation_unit = node.kind() == "translation_unit";
427    let mut fractured_function_scope = false;
428    let mut cursor = node.walk();
429    for child in node.named_children(&mut cursor) {
430        if translation_unit && fractured_function_scope && child.kind() == "ERROR" {
431            ctx.bindings.exit_scope();
432            ctx.local_shadows.exit_scope();
433            fractured_function_scope = false;
434        }
435        scan_node(child, ctx);
436        if *ctx.limit_exceeded {
437            break;
438        }
439        if translation_unit
440            && macro_fractured_function(child)
441            && macro_fracture_boundary(child).is_some()
442        {
443            if fractured_function_scope {
444                ctx.bindings.exit_scope();
445                ctx.local_shadows.exit_scope();
446            }
447            ctx.bindings.enter_scope();
448            ctx.local_shadows.enter_scope();
449            seed_fractured_function_prefix(child, ctx);
450            fractured_function_scope = true;
451        }
452    }
453    if fractured_function_scope {
454        ctx.bindings.exit_scope();
455        ctx.local_shadows.exit_scope();
456    }
457
458    if enters_scope {
459        ctx.bindings.exit_scope();
460        ctx.local_shadows.exit_scope();
461    }
462    if enters_using_enum_scope {
463        ctx.using_enum_owners.exit_scope();
464    }
465}
466
467fn macro_fractured_function(node: Node<'_>) -> bool {
468    if node.kind() != "function_definition" {
469        return false;
470    }
471    let Some(body) = node.child_by_field_name("body") else {
472        return false;
473    };
474    let mut stack = vec![body];
475    while let Some(current) = stack.pop() {
476        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
477            return true;
478        }
479        let mut cursor = current.walk();
480        stack.extend(current.named_children(&mut cursor));
481    }
482    false
483}
484
485/// Return the recovery boundary following a fractured macro-generated callable.
486pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
487    let first_orphan = node.next_named_sibling()?;
488    if !matches!(
489        first_orphan.kind(),
490        "expression_statement"
491            | "if_statement"
492            | "for_statement"
493            | "while_statement"
494            | "do_statement"
495            | "return_statement"
496    ) {
497        return None;
498    }
499    let mut sibling = Some(first_orphan);
500    while let Some(current) = sibling {
501        if current.kind() == "ERROR" {
502            return Some(current.end_byte());
503        }
504        if matches!(
505            current.kind(),
506            "function_definition" | "declaration" | "type_definition"
507        ) {
508            return None;
509        }
510        sibling = current.next_named_sibling();
511    }
512    None
513}
514
515fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
516    let cutoff = node.end_byte().saturating_sub(1);
517    seed_fractured_active_path(node, cutoff, ctx);
518}
519
520fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
521    if node.start_byte() >= cutoff {
522        return;
523    }
524    let enters_scope = matches!(
525        node.kind(),
526        "compound_statement"
527            | "function_definition"
528            | "lambda_expression"
529            | "for_range_loop"
530            | "for_statement"
531            | "while_statement"
532            | "if_statement"
533            | "class_specifier"
534            | "struct_specifier"
535            | "union_specifier"
536    );
537    if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
538        return;
539    }
540    seed_declarations(node, ctx);
541    let mut cursor = node.walk();
542    for child in node.named_children(&mut cursor) {
543        if child.start_byte() >= cutoff {
544            break;
545        }
546        seed_fractured_active_path(child, cutoff, ctx);
547    }
548}
549
550fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
551    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
552        || (ctx.spec.kind != TargetKind::Type
553            && indexed_recovered_class_field_declaration(node, ctx))
554    {
555        return;
556    }
557    match node.kind() {
558        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
559        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
560        "for_range_loop" => seed_range_binding(node, ctx),
561        "expression_statement" => seed_function_macro_local_binding(node, ctx),
562        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
563        "using_declaration" => seed_using_enum(node, ctx),
564        _ => {}
565    }
566}
567
568fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
569    let Some(binding) = ctx
570        .visibility
571        .function_macro_local_binding(ctx.file, node, ctx.source)
572    else {
573        return;
574    };
575    if ctx.spec.kind == TargetKind::Type {
576        ctx.bindings.declare_shadow(binding.name);
577        return;
578    }
579    let normalized = normalize_cpp_type_name(&binding.type_name);
580    let unit = binding
581        .type_node
582        .and_then(|type_node| {
583            ctx.visibility
584                .resolve_type_node_result(ctx.file, type_node, ctx.source)
585                .ok()
586                .flatten()
587        })
588        .or_else(|| {
589            ctx.visibility
590                .canonical_type_for_reference(ctx.file, &normalized)
591        })
592        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
593    ctx.bindings.seed_symbol(
594        binding.name,
595        CppScanBinding::from_type_name(
596            normalized,
597            unit,
598            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
599        ),
600    );
601}
602
603fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
604    let Some(binding) =
605        ctx.visibility
606            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
607    else {
608        return;
609    };
610    let normalized = normalize_cpp_type_name(&binding.type_name);
611    let unit = binding.proven_unit.clone().or_else(|| {
612        binding
613            .type_node
614            .and_then(|type_node| {
615                ctx.visibility
616                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
617                    .ok()
618                    .flatten()
619            })
620            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
621    });
622    if let Some(unit) = unit {
623        ctx.bindings.seed_symbol(
624            binding.name,
625            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
626        );
627    }
628}
629
630fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
631    if node.kind() != "declaration"
632        || !has_function_scope_ancestor(node)
633        || has_ancestor_kind(node, "lambda_expression")
634        || !(has_recovered_class_shape_ancestor(node)
635            || has_malformed_wrapper_function_definition_ancestor(node))
636    {
637        return false;
638    }
639    let context = enclosing_context(node, ctx);
640    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
641        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
642}
643
644fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
645    if !ctx.needs_using_enum_member_resolution {
646        return;
647    }
648    if let Some((unit, UsingEnumDeclarationScope::Block)) =
649        resolved_using_enum_declaration(node, ctx)
650    {
651        ctx.using_enum_owners.import(unit);
652    }
653}
654
655fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
656    // Class and local-struct fields are resolved from their enclosing owner
657    // when they appear as an unqualified receiver. Seeding them into the
658    // function-wide binding scope would make same-spelled fields from sibling
659    // owners overwrite one another before the owner-aware path runs.
660    if node.kind() == "field_declaration" {
661        return;
662    }
663    let type_node = node
664        .child_by_field_name("type")
665        .or_else(|| first_type_child(node));
666    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
667    let mut cursor = node.walk();
668    for child in node.named_children(&mut cursor) {
669        let declarator = declaration_declarator(node, child);
670        let Some(declarator) = declarator else {
671            continue;
672        };
673        let Some(name) = extract_variable_name(declarator, ctx.source) else {
674            continue;
675        };
676        if declarator.kind() == "function_declarator"
677            && !constructor_style_local_declaration(
678                ctx.visibility,
679                ctx.file,
680                ctx.source,
681                declarator,
682                type_text.as_deref(),
683                &ctx.bindings,
684            )
685        {
686            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
687                ctx.local_shadows.declare_shadow(name);
688            }
689            continue;
690        }
691        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
692            ctx.local_shadows.declare_shadow(name.clone());
693        }
694        if ctx.spec.kind == TargetKind::Type {
695            ctx.bindings.declare_shadow(name);
696            continue;
697        }
698        let value = child.child_by_field_name("value");
699        seed_binding_from_type_or_value(&name, type_node, value, ctx);
700    }
701}
702
703fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
704    if !parameter_belongs_to_callable_scope(node) {
705        return;
706    }
707    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
708        return;
709    };
710    if has_function_scope_ancestor(node) {
711        ctx.local_shadows.declare_shadow(name.clone());
712    }
713    if ctx.spec.kind == TargetKind::Type {
714        ctx.bindings.declare_shadow(name);
715        return;
716    }
717    seed_binding_from_type_or_value(&name, type_node, None, ctx);
718}
719
720fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
721    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
722        return;
723    };
724    if has_function_scope_ancestor(node) {
725        ctx.local_shadows.declare_shadow(name.clone());
726    }
727    if ctx.spec.kind == TargetKind::Type {
728        ctx.bindings.declare_shadow(name);
729        return;
730    }
731    seed_binding_from_type_or_value(&name, type_node, None, ctx);
732}
733
734fn has_function_scope_ancestor(node: Node<'_>) -> bool {
735    let mut current = node.parent();
736    while let Some(parent) = current {
737        // Error recovery can wrap a complete namespace in a bogus outer
738        // `function_definition` (for example when an object-like namespace
739        // macro is parsed as a return type).  Declarations below that
740        // namespace are still namespace-scoped: do not let the malformed
741        // callable envelope seed them as local shadows.  A real function
742        // body is encountered before its enclosing namespace, so this keeps
743        // ordinary local binding detection unchanged.
744        if parent.kind() == "namespace_definition" {
745            return false;
746        }
747        if parent.kind() == "function_definition" {
748            // The malformed sentinel envelope is not a callable scope. Its
749            // body may contain a recovered namespace (or, in a smaller error
750            // tree, only an ERROR node standing in for that namespace), so
751            // declarations directly below it must remain namespace-scoped.
752            // Real nested functions are encountered first and still seed
753            // ordinary local bindings.
754            return !is_malformed_wrapper_function_definition(parent);
755        }
756        if parent.kind() == "lambda_expression" {
757            return true;
758        }
759        current = parent.parent();
760    }
761    false
762}
763
764fn seed_binding_from_type_or_value(
765    name: &str,
766    type_node: Option<Node<'_>>,
767    value: Option<Node<'_>>,
768    ctx: &mut ScanCtx<'_>,
769) {
770    if name.is_empty() {
771        return;
772    }
773    let resolved = type_node
774        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
775        .map(|node| {
776            let text = node_text(node, ctx.source);
777            let name = normalize_cpp_type_name(text);
778            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
779                return CppScanBinding::from_type_name(
780                    name,
781                    Some(unit),
782                    cpp_type_text_pointer_depth(text),
783                );
784            }
785            // Bare type names need lexical ownership before the coarse visible-name
786            // fallback (two namespaces can each declare `CopyResult`). Template
787            // references keep the specialization-aware resolver first because a
788            // component-only lexical lookup cannot rank partial specializations.
789            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
790                if cpp_template_reference_arguments(node, ctx.source).is_some() {
791                    return None;
792                }
793                match enclosing_lexical_scope_components(
794                    node,
795                    &ctx.analyzer,
796                    ctx.visibility,
797                    ctx.file,
798                    ctx.source,
799                ) {
800                    LexicalScopeResolution::Resolved(scope) => Some(scope),
801                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
802                }
803            });
804            let unit = lexical_scope
805                .as_deref()
806                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
807                .or_else(|| {
808                    match ctx
809                        .visibility
810                        .resolve_type_node_result(ctx.file, node, ctx.source)
811                    {
812                        Ok(Some(unit)) => Some(unit),
813                        Ok(None) => ctx
814                            .visibility
815                            .canonical_type_for_reference(ctx.file, &name)
816                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
817                        Err(_) => None,
818                    }
819                });
820            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
821        })
822        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
823
824    if let Some(resolved) = resolved {
825        ctx.bindings.seed_symbol(name.to_string(), resolved);
826    } else if let Some(value) = value
827        && value.kind() == "identifier"
828    {
829        ctx.bindings
830            .alias_symbol(name.to_string(), node_text(value, ctx.source));
831    } else {
832        ctx.bindings.declare_shadow(name.to_string());
833    }
834}
835
836fn resolve_seed_type_node_lexically(
837    node: Node<'_>,
838    ctx: &ScanCtx<'_>,
839    scope: &[String],
840) -> Option<CodeUnit> {
841    let (components, global) = type_reference_components(node, ctx.source)?;
842    let resolution = match scan_owner_type(ctx) {
843        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
844            &ctx.analyzer,
845            ctx.file,
846            &components,
847            global,
848            scope,
849            target,
850        ),
851        None => ctx.visibility.resolve_type_components_lexically(
852            &ctx.analyzer,
853            ctx.file,
854            &components,
855            global,
856            scope,
857        ),
858    };
859    match resolution {
860        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
861        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
862    }
863}
864
865/// The type this scan is asking about: the owner of a member target, or the
866/// target itself when it is a type.
867///
868/// C headers routinely declare one typedef name once per branch of an
869/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
870/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
871/// are alternate spellings of one name rather than competing declarations, and
872/// canonicalizing without the scan's own type identity picks between them by
873/// declaration order. That ordering can bind a receiver to the branch that does
874/// not declare the queried member, which drops every reference in the branch
875/// that does. Naming the identity keeps the target-preserving rule the forward
876/// resolver already applies (`unique_type_candidate_preserving_target`).
877fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
878    // Target-preserving lookup can reconcile logical declarations across files.
879    // A receiver must not borrow an owner from a header its caller cannot see.
880    ctx.spec
881        .owner
882        .as_ref()
883        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
884}
885
886const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
887
888fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
889    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
890}
891
892fn infer_type_from_value_with_budget(
893    node: Node<'_>,
894    ctx: &ScanCtx<'_>,
895    remaining_call_depth: usize,
896) -> Option<CppScanBinding> {
897    match node.kind() {
898        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
899            infer_cpp_initializer_binding(
900                &ctx.analyzer,
901                ctx.visibility,
902                ctx.file,
903                ctx.source,
904                node,
905                None,
906            )
907        }
908        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
909            &ctx.analyzer,
910            ctx.visibility,
911            ctx.file,
912            ctx.source,
913            node,
914            Some(&|receiver, source| {
915                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
916            }),
917        ),
918        "initializer_list" => None,
919        "identifier" => {
920            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
921            resolved
922                .as_precise()?
923                .iter()
924                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
925                .cloned()
926        }
927        _ => {
928            let text = node_text(node, ctx.source);
929            let name = normalize_cpp_type_name(text);
930            ctx.visibility
931                .resolve_type(ctx.file, &name)
932                .map(|unit| CppScanBinding::from_unit(unit, 0))
933        }
934    }
935}
936
937/// Whether a member named `identifier` can be spelled anywhere in this file's
938/// syntax.
939///
940/// Both member scans below -- [`maybe_record_method_hit`] and
941/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
942/// of a syntax node against the target's member name, so a file whose syntax
943/// never spells that name yields neither a proven nor an unproven hit. That
944/// makes this an exact admission test rather than an approximation: candidate
945/// discovery can drop such a file without narrowing what the query proves.
946///
947/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
948/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
949/// replacement, which
950/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
951/// sentinel parse, and an `ERROR` region -- so those admit on containment
952/// instead of on a token match. A `comment` is the one subtree no scan reads,
953/// so it is skipped.
954///
955/// Defined only for a plain identifier: [`name_matches_callable`] widens the
956/// comparison for an `operator` name to the `operator` token alone, and a
957/// destructor or conversion-function name is not spelled the way it is
958/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
959/// that condition.
960pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
961    debug_assert!(
962        cpp_member_is_spelled_at_references(identifier),
963        "member admission is defined only for a plain identifier: {identifier:?}"
964    );
965    let mut spelled = false;
966    walk_named_tree_preorder(root, true, |node| match node.kind() {
967        "comment" => WalkControl::SkipChildren,
968        "identifier"
969        | "field_identifier"
970        | "type_identifier"
971        | "namespace_identifier"
972        | "statement_identifier" => {
973            if node_text(node, source).trim() == identifier {
974                spelled = true;
975                WalkControl::Break
976            } else {
977                WalkControl::SkipChildren
978            }
979        }
980        "preproc_arg" | "ERROR" => {
981            if node_text(node, source).contains(identifier) {
982                spelled = true;
983                WalkControl::Break
984            } else {
985                // The node's own text covers every descendant's bytes, so a
986                // miss here is a miss for the whole subtree.
987                WalkControl::SkipChildren
988            }
989        }
990        _ => WalkControl::Continue,
991    });
992    spelled
993}
994
995/// Whether every reference to a member named `identifier` must spell it.
996///
997/// An `operator` name is matched by [`name_matches_callable`] through the bare
998/// `operator` token, and a destructor or conversion function is recorded under
999/// a name its call sites do not write, so neither admits a spelling test.
1000pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1001    let mut characters = identifier.chars();
1002    characters
1003        .next()
1004        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1005        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1006        && !identifier.starts_with("operator")
1007}
1008
1009fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1010    match ctx.spec.kind {
1011        TargetKind::Type => maybe_record_type_hit(node, ctx),
1012        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1013        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1014        TargetKind::Method => maybe_record_method_hit(node, ctx),
1015        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1016        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1017        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1018    }
1019}
1020
1021fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1022    if node_text(node, ctx.source) != ctx.spec.member_name {
1023        return;
1024    }
1025    if is_ordinary_macro_reference_node(node) {
1026        if ctx.visibility.macro_binding_matches_target_declaration_at(
1027            ctx.file,
1028            &ctx.spec.member_name,
1029            node.start_byte(),
1030            ctx.spec.target.source(),
1031            &ctx.target_macro_declaration_bytes,
1032        ) {
1033            *ctx.raw_match_count += 1;
1034            push_hit(node, ctx);
1035            return;
1036        }
1037        match ctx.visibility.resolve_ordinary_macro_reference(
1038            &ctx.analyzer,
1039            ctx.file,
1040            node,
1041            ctx.source,
1042        ) {
1043            OrdinaryMacroReferenceResolution::Resolved(unit)
1044                if ctx.target_group.contains(&unit) =>
1045            {
1046                *ctx.raw_match_count += 1;
1047                push_hit(node, ctx);
1048            }
1049            OrdinaryMacroReferenceResolution::Ambiguous
1050                if ctx
1051                    .visibility
1052                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1053            {
1054                *ctx.raw_match_count += 1;
1055                push_unproven_hit(node, ctx);
1056            }
1057            OrdinaryMacroReferenceResolution::Resolved(_)
1058            | OrdinaryMacroReferenceResolution::Ambiguous
1059            | OrdinaryMacroReferenceResolution::Missing => {}
1060        }
1061        return;
1062    }
1063    if !matches!(
1064        node.kind(),
1065        "identifier"
1066            | "field_identifier"
1067            | "type_identifier"
1068            | "namespace_identifier"
1069            | "preproc_arg"
1070    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1071        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1072            && parent
1073                .child_by_field_name("name")
1074                .is_some_and(|name| same_node(name, node))
1075    }) {
1076        return;
1077    }
1078    if ctx.visibility.macro_binding_matches_target_declaration_at(
1079        ctx.file,
1080        &ctx.spec.member_name,
1081        node.start_byte(),
1082        ctx.spec.target.source(),
1083        &ctx.target_macro_declaration_bytes,
1084    ) {
1085        *ctx.raw_match_count += 1;
1086        push_hit(node, ctx);
1087    } else if ctx.visibility.macro_name_may_be_bound_at(
1088        ctx.file,
1089        &ctx.spec.member_name,
1090        node.start_byte(),
1091    ) && ctx
1092        .visibility
1093        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1094    {
1095        *ctx.raw_match_count += 1;
1096        push_unproven_hit(node, ctx);
1097    }
1098}
1099
1100fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1101    let kind = node.kind();
1102    if ctx.uses_c_semantics
1103        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1104            .is_some()
1105    {
1106        return;
1107    }
1108    if kind == "preproc_arg" {
1109        maybe_record_object_macro_replacement_type_hits(node, ctx);
1110        return;
1111    }
1112    if let Some(type_node) = ctx
1113        .visibility
1114        .function_macro_type_argument(ctx.file, node, ctx.source)
1115    {
1116        if ctx
1117            .local_shadows
1118            .is_shadowed(node_text(type_node, ctx.source))
1119        {
1120            return;
1121        }
1122        if let LexicalTypeResolution::Resolved {
1123            unit, candidates, ..
1124        } = resolve_type_node_lexically_for_target(
1125            type_node,
1126            &ctx.analyzer,
1127            ctx.visibility,
1128            &ctx.ordinary_type_imports,
1129            ctx.file,
1130            ctx.source,
1131            &ctx.spec.target,
1132            Some(&ctx.lexical_scope_cache),
1133            ctx.recovered_sentinel_scope(type_node).as_deref(),
1134        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1135        {
1136            *ctx.raw_match_count += 1;
1137            push_type_hit(type_node, ctx);
1138        }
1139        return;
1140    }
1141    let recovered_exported_class_base =
1142        matches!(
1143            kind,
1144            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1145        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1146    if kind == "field_declaration"
1147        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1148    {
1149        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1150        return;
1151    }
1152    if kind == "qualified_identifier"
1153        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1154    {
1155        // A member-pointer owner can itself end in a nested alias, as in
1156        // `type_identity<T>::type::*`. Resolving the complete owner
1157        // canonicalizes that alias to its underlying type and loses the alias
1158        // declaration that inverse lookup is targeting. Retain every
1159        // structurally proven qualifier component before asking for the
1160        // canonical owner type.
1161        if ctx
1162            .analyzer
1163            .type_alias_provider()
1164            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1165            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1166            && let Some(terminal) = owner.nodes.last().copied()
1167        {
1168            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1169                return;
1170            }
1171            if ctx.visibility.external_type_candidate_visible_in_context(
1172                &ctx.analyzer,
1173                ctx.file,
1174                &ctx.spec.target,
1175                terminal,
1176            ) || ctx
1177                .visibility
1178                .dependent_member_pointer_alias_visible_in_context(
1179                    &ctx.analyzer,
1180                    ctx.file,
1181                    &ctx.spec.target,
1182                    &owner.names,
1183                    terminal,
1184                )
1185            {
1186                *ctx.raw_match_count += 1;
1187                push_type_hit(terminal, ctx);
1188            }
1189            return;
1190        }
1191        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1192            *ctx.raw_match_count += 1;
1193            for scope in scopes {
1194                push_type_hit(scope, ctx);
1195            }
1196            return;
1197        }
1198        return;
1199    }
1200    if kind == "pointer_expression"
1201        && let Some(value) = qualified_callable_value(node)
1202        && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1203    {
1204        *ctx.raw_match_count += 1;
1205        match owner_hit {
1206            TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1207            TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1208        }
1209        return;
1210    }
1211    if kind == "call_expression" {
1212        maybe_record_direct_temporary_type_hit(node, ctx);
1213        return;
1214    }
1215    if !matches!(
1216        kind,
1217        "identifier"
1218            | "namespace_identifier"
1219            | "qualified_identifier"
1220            | "scoped_identifier"
1221            | "scoped_type_identifier"
1222            | "template_function"
1223            | "template_type"
1224            | "type_descriptor"
1225            | "type_identifier"
1226            | "using_declaration"
1227    ) {
1228        return;
1229    }
1230    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1231    let template_function_type = cpp_template_function_has_type_owner(node);
1232    let recovered_qualified_friend =
1233        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1234    if !recovered_type
1235        && !recovered_qualified_friend
1236        && !recovered_exported_class_base
1237        && matches!(
1238            kind,
1239            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1240        )
1241        && !type_reference_components_may_name_target(node, ctx)
1242    {
1243        return;
1244    }
1245    if ctx.ancestry.parent(node).is_some_and(|parent| {
1246        parent.kind() == "operator_cast"
1247            && parent
1248                .child_by_field_name("type")
1249                .is_some_and(|target| same_node(target, node))
1250    }) {
1251        return;
1252    }
1253    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1254        *ctx.raw_match_count += 1;
1255        push_type_hit(hit, ctx);
1256        return;
1257    }
1258    if matches!(kind, "identifier" | "template_function")
1259        && parent_if_field(node, "call_expression", "function").is_some()
1260    {
1261        return;
1262    }
1263    if kind == "using_declaration" {
1264        let (resolution, type_node) =
1265            if let Some(type_node) = using_enum_declaration_type_node(node) {
1266                (
1267                    resolve_using_enum_declaration_owner(
1268                        node,
1269                        &ctx.analyzer,
1270                        ctx.visibility,
1271                        &ctx.ordinary_type_imports,
1272                        ctx.file,
1273                        ctx.source,
1274                    ),
1275                    type_node,
1276                )
1277            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1278                (
1279                    resolve_ordinary_using_declaration_owner(
1280                        node,
1281                        &ctx.analyzer,
1282                        ctx.visibility,
1283                        ctx.file,
1284                        ctx.source,
1285                    ),
1286                    type_node,
1287                )
1288            } else {
1289                return;
1290            };
1291        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1292            && same_visible_symbol(&unit, &ctx.spec.target)
1293        {
1294            *ctx.raw_match_count += 1;
1295            push_type_hit(type_node, ctx);
1296        }
1297        return;
1298    }
1299    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1300        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1301            || local_type_name_shadows(node, ctx)
1302        {
1303            return;
1304        }
1305        if let LexicalTypeResolution::Resolved {
1306            unit, candidates, ..
1307        } = resolve_type_node_lexically_for_target(
1308            node,
1309            &ctx.analyzer,
1310            ctx.visibility,
1311            &ctx.ordinary_type_imports,
1312            ctx.file,
1313            ctx.source,
1314            &ctx.spec.target,
1315            Some(&ctx.lexical_scope_cache),
1316            ctx.recovered_sentinel_scope(node).as_deref(),
1317        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1318        {
1319            *ctx.raw_match_count += 1;
1320            push_type_hit(node, ctx);
1321        }
1322        return;
1323    }
1324    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1325        // A missing `::` can put either the macro or the real type in the
1326        // recovered scope. Resolve both candidates against this inverse
1327        // target before choosing one: a unique match is routed through the
1328        // ordinary target-guided path, while two distinct matches are
1329        // ambiguous and must not invent a hit. With no target match, retain
1330        // the existing recovered-scope path below for its conservative
1331        // fallback behaviour.
1332        let mut matching = Vec::new();
1333        for candidate in [node, type_node] {
1334            if matching
1335                .iter()
1336                .any(|existing| same_node(*existing, candidate))
1337            {
1338                continue;
1339            }
1340            if let LexicalTypeResolution::Resolved {
1341                unit, candidates, ..
1342            } = resolve_type_node_lexically_for_target(
1343                candidate,
1344                &ctx.analyzer,
1345                ctx.visibility,
1346                &ctx.ordinary_type_imports,
1347                ctx.file,
1348                ctx.source,
1349                &ctx.spec.target,
1350                Some(&ctx.lexical_scope_cache),
1351                ctx.recovered_sentinel_scope(candidate).as_deref(),
1352            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1353            {
1354                matching.push(candidate);
1355            }
1356        }
1357        match matching.as_slice() {
1358            [candidate] if !same_node(*candidate, node) => {
1359                maybe_record_type_hit(*candidate, ctx);
1360                return;
1361            }
1362            [candidate] if same_node(*candidate, node) => {}
1363            [] => {}
1364            _ => return,
1365        }
1366    }
1367    if !recovered_type
1368        && !template_function_type
1369        && !matches!(
1370            kind,
1371            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1372        )
1373    {
1374        return;
1375    }
1376    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1377        components.len() == 1 && !global && local_type_name_shadows(node, ctx)
1378    }) {
1379        return;
1380    }
1381    if !recovered_type
1382        && !recovered_qualified_friend
1383        && !recovered_exported_class_base
1384        && ctx.ancestry.parent(node).is_some_and(|parent| {
1385            parent.kind() == "alias_declaration"
1386                && parent
1387                    .child_by_field_name("name")
1388                    .is_some_and(|name| same_node(name, node))
1389        })
1390    {
1391        return;
1392    }
1393    #[cfg(any(test, feature = "test-support"))]
1394    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1395    if !recovered_type
1396        && !recovered_qualified_friend
1397        && !recovered_exported_class_base
1398        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1399        && is_declaration_name(node)
1400        && let Some(owners) = out_of_line_member_definition_owner(
1401            &ctx.analyzer,
1402            ctx.visibility,
1403            ctx.file,
1404            ctx.source,
1405            node,
1406        )
1407    {
1408        *ctx.raw_match_count += 1;
1409        let mut matched_owner = false;
1410        for (owner_node, owner) in owners.owners {
1411            if same_visible_symbol(&owner, &ctx.spec.target) {
1412                matched_owner = true;
1413                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1414            }
1415        }
1416        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1417            for scope in scopes {
1418                push_hit(scope, ctx);
1419            }
1420        } else if !matched_owner
1421            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1422        {
1423            push_unproven_hit(scope, ctx);
1424        }
1425        return;
1426    }
1427    if !recovered_type
1428        && !recovered_qualified_friend
1429        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1430        && is_declaration_name(node)
1431        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1432    {
1433        *ctx.raw_match_count += 1;
1434        push_type_hit(owner, ctx);
1435        return;
1436    }
1437    // A qualified template-id is represented as a qualified_identifier whose
1438    // name child is the template_type. Resolve the complete qualified
1439    // reference from that inner template node below; handling the outer node
1440    // independently would either lose the qualifier or emit a duplicate,
1441    // wider hit range.
1442    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1443        && matches!(
1444            node.kind(),
1445            "qualified_identifier" | "scoped_type_identifier"
1446        )
1447        && node
1448            .child_by_field_name("name")
1449            .is_some_and(|name| name.kind() == "template_type")
1450    {
1451        return;
1452    }
1453    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1454        *ctx.raw_match_count += 1;
1455        push_unproven_hit(scope, ctx);
1456        return;
1457    }
1458    if !recovered_type
1459        && !is_nested_type_node(node)
1460        && matches!(
1461            node.kind(),
1462            "qualified_identifier" | "scoped_type_identifier"
1463        )
1464        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1465    {
1466        *ctx.raw_match_count += 1;
1467        for scope in scopes {
1468            push_type_hit(scope, ctx);
1469        }
1470        return;
1471    }
1472    if !recovered_type && is_nested_type_node(node) {
1473        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1474            *ctx.raw_match_count += 1;
1475            push_type_hit(hit, ctx);
1476            return;
1477        }
1478        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1479            *ctx.raw_match_count += 1;
1480            push_type_hit(hit, ctx);
1481            return;
1482        }
1483        // A concrete template specialization can be absent from the coarse
1484        // per-file component-name index: that index contains the primary name
1485        // while this recovered child may expose only the malformed template
1486        // leaf.  Resolve the complete enclosing template before applying the
1487        // component prefilter so an exact target candidate can prove this
1488        // reference.  Non-specialization targets retain the cheap gate.
1489        let nested_template = if node.kind() == "template_type" {
1490            Some(node)
1491        } else {
1492            ctx.ancestry.parent(node).filter(|parent| {
1493                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1494            })
1495        };
1496        // Concrete specializations need their nested template-id inspected.
1497        // An ordinary alias application does too when it is itself the scope
1498        // of a member-qualified type, because the enclosing node denotes the
1499        // member rather than the alias. In every other primary/alias shape the
1500        // enclosing structured type owns the hit range; descending would emit
1501        // a duplicate terminal subrange.
1502        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1503            let enclosing_qualified_type_owns_range =
1504                ctx.ancestry.parent(template).is_some_and(|parent| {
1505                    matches!(
1506                        parent.kind(),
1507                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1508                    ) && parent.child_by_field_name("name") == Some(template)
1509                });
1510            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1511                return false;
1512            };
1513            let Some(name) = template_reference_name_node(template) else {
1514                return false;
1515            };
1516            let alias_candidates = ctx
1517                .visibility
1518                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1519                .filter(|candidate| alias_provider.is_type_alias(candidate))
1520                .cloned()
1521                .collect::<Vec<_>>();
1522            let direct_target_alias_visible = !alias_candidates
1523                .iter()
1524                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1525                || ctx.visibility.external_type_candidate_visible_in_context(
1526                    &ctx.analyzer,
1527                    ctx.file,
1528                    &ctx.spec.target,
1529                    template,
1530                );
1531            !enclosing_qualified_type_owns_range
1532                && direct_target_alias_visible
1533                && template_reference_candidates_select_target(
1534                    template,
1535                    &alias_candidates,
1536                    &ctx.analyzer,
1537                    ctx.visibility,
1538                    ctx.file,
1539                    ctx.source,
1540                    &ctx.spec.target,
1541                )
1542        });
1543        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1544            return;
1545        }
1546        if nested_alias_qualifier {
1547            let template = nested_template.expect("a nested alias qualifier has a template node");
1548            *ctx.raw_match_count += 1;
1549            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1550                .unwrap_or_else(|| type_reference_hit_node(template));
1551            push_type_hit(hit, ctx);
1552            return;
1553        }
1554        // For concrete specializations, the visible identifier index can
1555        // already contain the exact template spelling even when lexical
1556        // resolution rejects the recovered child (the child itself has no
1557        // argument list). Select the name as structured evidence;
1558        // `push_type_hit` expands it to the complete written template-id.
1559        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1560            && let Some(template) = nested_template
1561            && template_type_component_preserves_target(
1562                template,
1563                &ctx.visibility
1564                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1565                    .cloned()
1566                    .collect::<Vec<_>>(),
1567                ctx,
1568            )
1569        {
1570            *ctx.raw_match_count += 1;
1571            let hit = template
1572                .child_by_field_name("name")
1573                .filter(|name| name.kind() == "type_identifier")
1574                .unwrap_or(template);
1575            push_type_hit(hit, ctx);
1576            return;
1577        }
1578        if let Some(template) = nested_template
1579            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1580        {
1581            *ctx.raw_match_count += 1;
1582            let hit =
1583                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1584                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1585                        template
1586                            .child_by_field_name("name")
1587                            .filter(|name| name.kind() == "type_identifier")
1588                            .unwrap_or(template)
1589                    } else {
1590                        type_reference_hit_node(template)
1591                    }
1592                });
1593            push_type_hit(hit, ctx);
1594        }
1595        return;
1596    }
1597    if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1598        let direct_target = resolve_qualified_call_target(
1599            call,
1600            node,
1601            &ctx.analyzer,
1602            ctx.visibility,
1603            &ctx.ordinary_type_imports,
1604            ctx.file,
1605            ctx.source,
1606        );
1607        if matches!(direct_target, BareCallTargetResolution::Type(_))
1608            && let LexicalTypeResolution::Resolved {
1609                unit, candidates, ..
1610            } = resolve_type_node_lexically_for_target(
1611                node,
1612                &ctx.analyzer,
1613                ctx.visibility,
1614                &ctx.ordinary_type_imports,
1615                ctx.file,
1616                ctx.source,
1617                &ctx.spec.target,
1618                Some(&ctx.lexical_scope_cache),
1619                ctx.recovered_sentinel_scope(node).as_deref(),
1620            )
1621            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1622        {
1623            *ctx.raw_match_count += 1;
1624            push_resolved_type_hit(node, &candidates, ctx);
1625        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1626            *ctx.raw_match_count += 1;
1627            for scope in scopes {
1628                push_type_hit(scope, ctx);
1629            }
1630        } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1631            *ctx.raw_match_count += 1;
1632            match owner_hit {
1633                TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1634                TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1635                    push_unproven_hit(scope, ctx)
1636                }
1637            }
1638        }
1639        return;
1640    }
1641    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1642        *ctx.raw_match_count += 1;
1643        if proven {
1644            push_type_hit(hit, ctx);
1645        } else {
1646            push_unproven_hit(hit, ctx);
1647        }
1648        return;
1649    }
1650    if !recovered_type
1651        && !recovered_qualified_friend
1652        && !recovered_exported_class_base
1653        && is_declaration_name(node)
1654    {
1655        let mut matched_owner = false;
1656        if let Some(owners) = out_of_line_member_definition_owner(
1657            &ctx.analyzer,
1658            ctx.visibility,
1659            ctx.file,
1660            ctx.source,
1661            node,
1662        ) {
1663            for (owner_node, owner) in owners.owners {
1664                if same_visible_symbol(&owner, &ctx.spec.target) {
1665                    matched_owner = true;
1666                    *ctx.raw_match_count += 1;
1667                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1668                }
1669            }
1670        }
1671        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1672            *ctx.raw_match_count += 1;
1673            for scope in scopes {
1674                push_hit(scope, ctx);
1675            }
1676        } else if !matched_owner
1677            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1678        {
1679            *ctx.raw_match_count += 1;
1680            push_unproven_hit(scope, ctx);
1681        }
1682        return;
1683    }
1684    let hit_node = node;
1685    let text = node_text(hit_node, ctx.source);
1686    let type_resolution = if hit_node.kind() == "template_type"
1687        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1688    {
1689        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1690            resolve_type_node_lexically_for_target(
1691                hit_node,
1692                &ctx.analyzer,
1693                ctx.visibility,
1694                &ctx.ordinary_type_imports,
1695                ctx.file,
1696                ctx.source,
1697                &ctx.spec.target,
1698                Some(&ctx.lexical_scope_cache),
1699                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1700            )
1701        })
1702    } else {
1703        resolve_type_node_lexically_for_target(
1704            hit_node,
1705            &ctx.analyzer,
1706            ctx.visibility,
1707            &ctx.ordinary_type_imports,
1708            ctx.file,
1709            ctx.source,
1710            &ctx.spec.target,
1711            Some(&ctx.lexical_scope_cache),
1712            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1713        )
1714    };
1715    match type_resolution {
1716        LexicalTypeResolution::Resolved {
1717            unit, candidates, ..
1718        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1719            *ctx.raw_match_count += 1;
1720            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1721                for scope in scopes {
1722                    push_type_hit(scope, ctx);
1723                }
1724            } else {
1725                push_resolved_type_hit(hit_node, &candidates, ctx);
1726            }
1727            return;
1728        }
1729        LexicalTypeResolution::Resolved {
1730            unit: _,
1731            candidates,
1732            ..
1733        } => {
1734            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1735                *ctx.raw_match_count += 1;
1736                for scope in scopes {
1737                    push_type_hit(scope, ctx);
1738                }
1739            } else if let Some(hit) =
1740                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1741            {
1742                *ctx.raw_match_count += 1;
1743                push_unproven_hit(hit, ctx);
1744            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1745                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1746            {
1747                *ctx.raw_match_count += 1;
1748                push_type_hit(leaf, ctx);
1749            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1750                *ctx.raw_match_count += 1;
1751                push_unproven_hit(leaf, ctx);
1752            }
1753            return;
1754        }
1755        LexicalTypeResolution::Ambiguous => {
1756            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1757                *ctx.raw_match_count += 1;
1758                for scope in scopes {
1759                    push_type_hit(scope, ctx);
1760                }
1761            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1762                *ctx.raw_match_count += 1;
1763                push_unproven_hit(leaf, ctx);
1764            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1765                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1766                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1767            {
1768                *ctx.raw_match_count += 1;
1769                push_type_hit(leaf, ctx);
1770            }
1771            return;
1772        }
1773        LexicalTypeResolution::Missing => {
1774            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1775                &ctx.analyzer,
1776                ctx.file,
1777                hit_node,
1778                ctx.source,
1779            ) && same_visible_symbol(&unit, &ctx.spec.target)
1780            {
1781                *ctx.raw_match_count += 1;
1782                push_type_hit(hit_node, ctx);
1783                return;
1784            }
1785            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1786                *ctx.raw_match_count += 1;
1787                push_unproven_hit(leaf, ctx);
1788                return;
1789            }
1790            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1791                *ctx.raw_match_count += 1;
1792                push_unproven_hit(leaf, ctx);
1793                return;
1794            }
1795            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1796                *ctx.raw_match_count += 1;
1797                push_type_hit(leaf, ctx);
1798                return;
1799            }
1800            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1801                hit_node,
1802                &ctx.analyzer,
1803                ctx.visibility,
1804                ctx.file,
1805                ctx.source,
1806                &ctx.spec.target,
1807            );
1808            let raw_matches = matches!(
1809                raw_resolution,
1810                LexicalTypeResolution::Resolved {
1811                    ref unit,
1812                    ref candidates,
1813                    ..
1814                } if type_resolution_identifies_unit_target(
1815                    hit_node,
1816                    unit,
1817                    candidates,
1818                    &ctx.spec.target,
1819                    ctx,
1820                )
1821            );
1822            if raw_matches
1823                || type_node_has_exact_target_identity_without_visibility(
1824                    hit_node,
1825                    &ctx.analyzer,
1826                    ctx.visibility,
1827                    ctx.file,
1828                    ctx.source,
1829                    &ctx.spec.target,
1830                )
1831            {
1832                *ctx.raw_match_count += 1;
1833                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1834                return;
1835            }
1836        }
1837    }
1838    // A class-owned alias can be qualified by an owner made visible through a
1839    // namespace import, including an import in an earlier reopening of the
1840    // same unnamed namespace. Resolve that owner before requiring an
1841    // enclosing class for inherited nested-type lookup.
1842    if ctx
1843        .visibility
1844        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1845    {
1846        *ctx.raw_match_count += 1;
1847        push_type_hit(type_reference_hit_node(hit_node), ctx);
1848        return;
1849    }
1850    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1851        *ctx.raw_match_count += 1;
1852        for scope in scopes {
1853            push_type_hit(scope, ctx);
1854        }
1855        return;
1856    }
1857    if !name_mentions(text, &ctx.spec.member_name) {
1858        return;
1859    }
1860    *ctx.raw_match_count += 1;
1861    if !ctx.visibility.external_type_candidate_visible_in_context(
1862        &ctx.analyzer,
1863        ctx.file,
1864        &ctx.spec.target,
1865        hit_node,
1866    ) {
1867        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1868        if type_reference_resolves_away_from_target(unproven, ctx) {
1869            return;
1870        }
1871        push_unproven_hit(unproven, ctx);
1872    }
1873}
1874
1875fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1876    for reference in object_macro_replacement_type_references(node, ctx.source) {
1877        for component_count in 1..=reference.components.len() {
1878            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1879                node,
1880                &reference.components[..component_count],
1881                reference.global,
1882                &ctx.analyzer,
1883                ctx.visibility,
1884                &ctx.ordinary_type_imports,
1885                ctx.file,
1886                ctx.source,
1887                &ctx.spec.target,
1888                false,
1889                Some(&ctx.lexical_scope_cache),
1890            );
1891            let matches_target = match resolution {
1892                LexicalTypeResolution::Resolved {
1893                    unit, candidates, ..
1894                } => {
1895                    same_visible_symbol(&unit, &ctx.spec.target)
1896                        || candidates
1897                            .iter()
1898                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1899                }
1900                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1901                    &ctx.analyzer,
1902                    ctx.visibility,
1903                    ctx.file,
1904                    &reference.components[..component_count],
1905                )
1906                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1907                LexicalTypeResolution::Ambiguous => false,
1908            };
1909            if !matches_target {
1910                continue;
1911            }
1912            let range = &reference.component_ranges[component_count - 1];
1913            *ctx.raw_match_count += 1;
1914            push_type_hit_range(node, range.start, range.end, ctx);
1915        }
1916    }
1917}
1918
1919fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1920    node: Node<'tree>,
1921    ctx: &ScanCtx<'_>,
1922) -> Option<Node<'tree>> {
1923    let (components, global) = type_reference_components(node, ctx.source)?;
1924    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1925        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1926            node,
1927            &ctx.analyzer,
1928            ctx.visibility,
1929            ctx.file,
1930            ctx.source,
1931            false,
1932            false,
1933            Some(&ctx.lexical_scope_cache),
1934        ) {
1935            LexicalScopeResolution::Resolved(scope) => Some(scope),
1936            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1937        }
1938    })?;
1939    let OrdinaryTypeImportResolution::Resolved { target, .. } =
1940        compatible_foreign_type_import_resolution(
1941            node,
1942            &components,
1943            global,
1944            &ctx.analyzer,
1945            ctx.visibility,
1946            &ctx.ordinary_type_imports,
1947            ctx.file,
1948            ctx.source,
1949            &lexical_scope,
1950            Some(&ctx.spec.target),
1951        )
1952    else {
1953        return None;
1954    };
1955    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
1956}
1957
1958/// Resolve `Owner::Nested` when complete type lookup cannot express the owner
1959/// as its first qualified component. This occurs when a using-directive
1960/// imports `Owner` and when inherited-type lookup injects it. Resolve the
1961/// owner prefix independently, then return the terminal node after that owner
1962/// proves the target's direct nested declaration; hit insertion expands it to
1963/// the complete written type.
1964fn target_guided_nested_type_terminal_hit<'tree>(
1965    node: Node<'tree>,
1966    ctx: &ScanCtx<'_>,
1967) -> Option<Node<'tree>> {
1968    // Returns a node of the caller's tree lifetime, which the context-scoped
1969    // ancestry index cannot name; this is one question per call.
1970    let qualified = node.parent().filter(|parent| {
1971        matches!(
1972            parent.kind(),
1973            "qualified_identifier" | "scoped_type_identifier"
1974        ) && parent.child_by_field_name("name") == Some(node)
1975    })?;
1976    let mut complete = qualified;
1977    while let Some(parent) = complete.parent().filter(|parent| {
1978        matches!(
1979            parent.kind(),
1980            "qualified_identifier" | "scoped_type_identifier"
1981        )
1982    }) {
1983        complete = parent;
1984    }
1985    let owner = qualified_owner_components(complete, ctx.source)?;
1986
1987    if let LexicalTypeResolution::Resolved {
1988        unit, candidates, ..
1989    } = resolve_type_node_lexically_for_target(
1990        complete,
1991        &ctx.analyzer,
1992        ctx.visibility,
1993        &ctx.ordinary_type_imports,
1994        ctx.file,
1995        ctx.source,
1996        &ctx.spec.target,
1997        Some(&ctx.lexical_scope_cache),
1998        ctx.recovered_sentinel_scope(complete).as_deref(),
1999    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2000        && ctx
2001            .analyzer
2002            .parent_of(&ctx.spec.target)
2003            .is_some_and(|parent| parent.is_class())
2004        && !ctx
2005            .analyzer
2006            .type_alias_provider()
2007            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2008        && is_namespace_scope_type_reference(complete)
2009        && namespace_import_can_name_nested_target(complete, ctx)
2010    {
2011        return Some(node);
2012    }
2013
2014    let target_owner = ctx
2015        .analyzer
2016        .parent_of(&ctx.spec.target)
2017        .filter(CodeUnit::is_class);
2018    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2019        let target_components = canonical_cpp_scope_components(target_owner);
2020        if owner.global {
2021            target_components == owner.names
2022        } else {
2023            target_components.ends_with(&owner.names)
2024        }
2025    });
2026    let target_member_visible = |target_owner: &CodeUnit| {
2027        ctx.visibility
2028            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2029            .into_iter()
2030            .any(|candidate| {
2031                same_visible_symbol(candidate, &ctx.spec.target)
2032                    && (ctx
2033                        .visibility
2034                        .external_type_candidate_guard_compatible_in_context(
2035                            &ctx.analyzer,
2036                            ctx.file,
2037                            candidate,
2038                            complete,
2039                        )
2040                        || (ctx
2041                            .visibility
2042                            .is_exhaustive_same_fqn_type_declaration_family(
2043                                &ctx.analyzer,
2044                                ctx.file,
2045                                candidate,
2046                            )
2047                            && ctx.visibility.external_type_candidate_visible_in_context(
2048                                &ctx.analyzer,
2049                                ctx.file,
2050                                candidate,
2051                                complete,
2052                            )))
2053            })
2054    };
2055
2056    if owner_spelling_can_name_target
2057        && (is_namespace_scope_type_reference(complete)
2058            || (is_compound_type_reference(complete)
2059                && namespace_import_can_name_nested_target(complete, ctx)))
2060        && let Some(target_owner) = target_owner.as_ref()
2061        && let LexicalTypeResolution::Resolved { unit, .. } =
2062            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2063                complete,
2064                &owner.names,
2065                owner.global,
2066                &ctx.analyzer,
2067                ctx.visibility,
2068                &ctx.ordinary_type_imports,
2069                ctx.file,
2070                ctx.source,
2071                Some(&ctx.lexical_scope_cache),
2072            )
2073        && ctx
2074            .visibility
2075            .same_template_owner_identity(&unit, target_owner)
2076        && target_member_visible(target_owner)
2077    {
2078        return Some(node);
2079    }
2080
2081    if ctx
2082        .analyzer
2083        .type_alias_provider()
2084        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2085        && !ctx
2086            .analyzer
2087            .parent_of(&ctx.spec.target)
2088            .is_some_and(|owner| owner.is_class())
2089        && let Some(target_owner) = target_owner.as_ref()
2090        && let LexicalTypeResolution::Resolved {
2091            unit, candidates, ..
2092        } = resolve_type_components_lexically_at_for_target_with_scope_cache(
2093            complete,
2094            &owner.names,
2095            owner.global,
2096            &ctx.analyzer,
2097            ctx.visibility,
2098            &ctx.ordinary_type_imports,
2099            ctx.file,
2100            ctx.source,
2101            target_owner,
2102            false,
2103            Some(&ctx.lexical_scope_cache),
2104        )
2105        && (ctx
2106            .visibility
2107            .same_template_owner_identity(&unit, target_owner)
2108            || candidates.iter().any(|candidate| {
2109                ctx.visibility
2110                    .same_template_owner_identity(candidate, target_owner)
2111            }))
2112        && target_member_visible(target_owner)
2113    {
2114        return Some(node);
2115    }
2116
2117    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2118    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2119    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2120        &ctx.analyzer,
2121        ctx.file,
2122        &owner.names,
2123        owner.global,
2124        &lexical_scope,
2125    );
2126    let owner_unit = match owner_resolution {
2127        LexicalTypeResolution::Resolved { unit, .. } => unit,
2128        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2129            ctx.visibility.inherited_injected_class_owner(
2130                &ctx.analyzer,
2131                ctx.file,
2132                &enclosing_owner,
2133                owner.names.first()?,
2134            )?
2135        }
2136        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2137    };
2138    let name = node_text(node, ctx.source);
2139    ctx.visibility
2140        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2141        .into_iter()
2142        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2143        .then_some(complete)
2144}
2145
2146fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2147    // Namespace-scope nested classes need a terminal edge when the complete
2148    // qualified node is the only structured evidence (as in #2213). In
2149    // parameters, fields, and callable bodies the enclosing type node already
2150    // owns the exact range; adding its terminal would duplicate that edge.
2151    let mut current = node.parent();
2152    while let Some(parent) = current {
2153        match parent.kind() {
2154            "call_expression" | "new_expression" => return true,
2155            "parameter_declaration"
2156            | "optional_parameter_declaration"
2157            | "field_declaration"
2158            | "function_definition"
2159            | "function_declarator"
2160            | "class_specifier"
2161            | "struct_specifier"
2162            | "union_specifier"
2163            | "compound_statement" => return false,
2164            "declaration" => {
2165                let mut cursor = parent.walk();
2166                if parent
2167                    .named_children(&mut cursor)
2168                    .any(|child| child.kind() == "function_declarator")
2169                {
2170                    return false;
2171                }
2172            }
2173            _ => {}
2174        }
2175        current = parent.parent();
2176    }
2177    true
2178}
2179
2180fn is_compound_type_reference(node: Node<'_>) -> bool {
2181    let mut current = node.parent();
2182    while let Some(parent) = current {
2183        match parent.kind() {
2184            "compound_statement" => return true,
2185            "namespace_definition" | "translation_unit" => return false,
2186            _ => current = parent.parent(),
2187        }
2188    }
2189    false
2190}
2191
2192fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2193    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2194        return false;
2195    };
2196    effective_using_bindings_for_name(
2197        ctx.visibility,
2198        &ctx.ordinary_type_imports,
2199        ctx.file,
2200        node,
2201        ctx.source,
2202        target_owner.identifier(),
2203    )
2204    .iter()
2205    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2206}
2207
2208/// Recover the class-template qualifier in a dependent leading return type of
2209/// an out-of-line member definition.
2210///
2211/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2212/// `Owner`: one in the return type and one in the callable declarator. The
2213/// latter already has authoritative indexed owner resolution. Reuse that
2214/// structured owner evidence for the former when both template names agree;
2215/// resolving `Owner<T>::Alias` as one type would instead select terminal
2216/// `Alias` and discard the qualifier.
2217fn out_of_line_dependent_return_template_owner<'tree>(
2218    node: Node<'tree>,
2219    ctx: &ScanCtx<'_>,
2220) -> Option<Node<'tree>> {
2221    if node.kind() != "template_type" {
2222        return None;
2223    }
2224    let template_name = template_reference_name_node(node)?;
2225    let mut scope = node;
2226    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2227        matches!(
2228            parent.kind(),
2229            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2230        ) && parent.child_by_field_name("name") == Some(scope)
2231    }) {
2232        scope = parent;
2233    }
2234    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2235        matches!(
2236            parent.kind(),
2237            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2238        ) && parent.child_by_field_name("scope") == Some(scope)
2239    })?;
2240    let mut function = ctx.ancestry.parent(qualified);
2241    let function = loop {
2242        let candidate = function?;
2243        if candidate.kind() == "function_definition" {
2244            break candidate;
2245        }
2246        function = ctx.ancestry.parent(candidate);
2247    };
2248    let return_type = function.child_by_field_name("type")?;
2249    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2250        return None;
2251    }
2252    let declarator_name = function
2253        .child_by_field_name("declarator")
2254        .and_then(declarator_name_node)?;
2255    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2256        return None;
2257    }
2258    let resolved_owner_matches = out_of_line_member_definition_owner(
2259        &ctx.analyzer,
2260        ctx.visibility,
2261        ctx.file,
2262        ctx.source,
2263        declarator_name,
2264    )
2265    .is_some_and(|owners| {
2266        owners
2267            .owners
2268            .iter()
2269            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2270    });
2271    let indexed_owner_matches =
2272        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2273    let target_guided_owner_matches =
2274        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2275    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2276        .then_some(template_name)
2277}
2278
2279/// Use the indexed callable identity when a malformed namespace sentinel
2280/// prevents lexical lookup of an out-of-line template owner.
2281fn indexed_out_of_line_template_owner_hit<'tree>(
2282    node: Node<'tree>,
2283    ctx: &ScanCtx<'_>,
2284) -> Option<Node<'tree>> {
2285    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2286        || !is_declaration_name(node)
2287    {
2288        return None;
2289    }
2290    let mut function = ctx.ancestry.parent(node);
2291    let function = loop {
2292        let candidate = function?;
2293        if candidate.kind() == "function_definition" {
2294            break candidate;
2295        }
2296        function = ctx.ancestry.parent(candidate);
2297    };
2298    if function
2299        .child_by_field_name("declarator")
2300        .and_then(declarator_name_node)
2301        != Some(node)
2302    {
2303        return None;
2304    }
2305    let target = physically_visible_type_target(ctx)?;
2306    let qualified = qualified_owner_components(node, ctx.source)?;
2307    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2308        return None;
2309    }
2310    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2311        != canonical_cpp_scope_components(target)
2312    {
2313        return None;
2314    }
2315    let owner = qualified.nodes.last().copied()?;
2316    let template = if owner.kind() == "template_type" {
2317        owner
2318    } else {
2319        owner
2320            .parent()
2321            .filter(|parent| parent.kind() == "template_type")?
2322    };
2323    template_reference_name_node(template)
2324}
2325
2326fn push_guarded_owner_hit(
2327    owner_node: Node<'_>,
2328    owner: &CodeUnit,
2329    reference: Node<'_>,
2330    ctx: &mut ScanCtx<'_>,
2331) {
2332    if ctx
2333        .visibility
2334        .external_type_candidate_guard_compatible_in_context(
2335            &ctx.analyzer,
2336            ctx.file,
2337            owner,
2338            reference,
2339        )
2340    {
2341        push_hit(owner_node, ctx);
2342    } else {
2343        push_unproven_hit(owner_node, ctx);
2344    }
2345}
2346
2347/// Preserve a direct template-alias reference when a macro namespace sentinel
2348/// makes tree-sitter drop the first source path component.
2349fn target_guided_alias_template_reference<'tree>(
2350    node: Node<'tree>,
2351    ctx: &ScanCtx<'_>,
2352) -> Option<(Node<'tree>, bool)> {
2353    let alias_provider = ctx.analyzer.type_alias_provider()?;
2354    if !matches!(
2355        node.kind(),
2356        "qualified_identifier" | "scoped_type_identifier"
2357    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2358    {
2359        return None;
2360    }
2361    cpp_template_reference_arguments(node, ctx.source)?;
2362    let (components, global) = type_reference_components(node, ctx.source)?;
2363    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2364        return None;
2365    }
2366    let target = physically_visible_type_target(ctx)?;
2367    let target_components = canonical_cpp_scope_components(target);
2368    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2369    let path_matches = if global {
2370        components == target_components
2371            || (!parser_namespace.is_empty()
2372                && target_components.starts_with(&parser_namespace)
2373                && target_components[parser_namespace.len()..] == components)
2374    } else {
2375        let lexical_scope = match enclosing_lexical_scope_components(
2376            node,
2377            &ctx.analyzer,
2378            ctx.visibility,
2379            ctx.file,
2380            ctx.source,
2381        ) {
2382            LexicalScopeResolution::Resolved(scope) => scope,
2383            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2384        };
2385        lexical_component_tiers(&components, false, &lexical_scope)
2386            .any(|scope| scope == target_components)
2387    };
2388    let scoped_candidates = ctx
2389        .visibility
2390        .visible_identifier_candidates(ctx.file, target.identifier())
2391        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2392        .collect::<Vec<_>>();
2393    if !path_matches
2394        || scoped_candidates.is_empty()
2395        || scoped_candidates
2396            .iter()
2397            .any(|candidate| !same_visible_symbol(candidate, target))
2398        || !ctx.visibility.structured_alias_primary_preserves_target(
2399            &ctx.analyzer,
2400            ctx.file,
2401            target,
2402            target,
2403        )
2404    {
2405        return None;
2406    }
2407    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2408        &ctx.analyzer,
2409        ctx.file,
2410        target,
2411        node,
2412    );
2413    Some((node, proven))
2414}
2415
2416/// Tree-sitter can split a macro-qualified member return type into a phantom
2417/// field followed by the real function definition.  The declaration visitor
2418/// discards that phantom field, but its declarator token remains a semantic
2419/// type reference. Resolve it from the recovered or indexed class scope so an
2420/// enclosing-class alias remains distinguishable from same-spelled siblings.
2421fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2422    let name = node_text(return_type, ctx.source);
2423    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2424        return;
2425    }
2426    if physically_visible_type_target(ctx).is_some()
2427        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2428        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2429        && ctx.visibility.external_type_candidate_visible_in_context(
2430            &ctx.analyzer,
2431            ctx.file,
2432            &ctx.spec.target,
2433            return_type,
2434        )
2435    {
2436        *ctx.raw_match_count += 1;
2437        push_type_hit(return_type, ctx);
2438        return;
2439    }
2440    let Some(scope) = ctx
2441        .recovered_sentinel_scope(return_type)
2442        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2443    else {
2444        return;
2445    };
2446    let components = [name.to_string()];
2447    let resolution = ctx.visibility.resolve_type_components_lexically(
2448        &ctx.analyzer,
2449        ctx.file,
2450        &components,
2451        false,
2452        &scope,
2453    );
2454    if let LexicalTypeResolution::Resolved {
2455        unit, candidates, ..
2456    } = resolution
2457        && (same_visible_symbol(&unit, &ctx.spec.target)
2458            || candidates
2459                .iter()
2460                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2461    {
2462        *ctx.raw_match_count += 1;
2463        push_type_hit(return_type, ctx);
2464    }
2465}
2466
2467/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2468/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2469/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2470/// than a type node; ordinary type-reference traversal therefore skips it.
2471fn member_pointer_owner_components<'tree>(
2472    node: Node<'tree>,
2473    source: &str,
2474) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2475    if node.kind() != "qualified_identifier" {
2476        return None;
2477    }
2478    let declarator = node.child_by_field_name("name")?;
2479    if !matches!(
2480        declarator.kind(),
2481        "pointer_type_declarator" | "abstract_pointer_declarator"
2482    ) {
2483        return None;
2484    }
2485    let scope = node.child_by_field_name("scope")?;
2486    // Keep this check entirely structural.  C and C++ share the
2487    // `qualified_identifier` node shape for some recovered declarations, but
2488    // only the C++ member-pointer form has an actual `::` grammar child
2489    // between the owner scope and pointer declarator.  Looking at the source
2490    // slice here would make a recovered C declarator look like a C++ owner
2491    // merely because its bytes happen to contain the same punctuation.
2492    let mut saw_scope = false;
2493    let has_scope_separator = (0..node.child_count()).any(|index| {
2494        let Some(child) = node.child(index) else {
2495            return false;
2496        };
2497        if same_node(child, scope) {
2498            saw_scope = true;
2499            return false;
2500        }
2501        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2502    });
2503    if !has_scope_separator {
2504        return None;
2505    }
2506    let mut nodes = cpp_name_component_nodes(scope)?;
2507    let mut outer = node;
2508    while let Some(parent) = outer.parent()
2509        && parent.kind() == "qualified_identifier"
2510        && parent.child_by_field_name("name") == Some(outer)
2511    {
2512        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2513        prefix.append(&mut nodes);
2514        nodes = prefix;
2515        outer = parent;
2516    }
2517    let names = nodes
2518        .iter()
2519        .map(|component| node_text(*component, source).to_string())
2520        .collect();
2521    Some((
2522        QualifiedOwnerComponents {
2523            nodes,
2524            names,
2525            global: is_globally_qualified_cpp_name(outer),
2526        },
2527        outer,
2528    ))
2529}
2530
2531fn member_pointer_alias_owner_prefix_matches(
2532    node: Node<'_>,
2533    owner: &QualifiedOwnerComponents<'_>,
2534    ctx: &ScanCtx<'_>,
2535) -> bool {
2536    let Some((_, owner_prefix)) = owner.names.split_last() else {
2537        return false;
2538    };
2539    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2540        return false;
2541    };
2542    let recovered_scope = ctx.recovered_sentinel_scope(node);
2543    let resolution = if let Some(recovered_scope) = recovered_scope {
2544        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2545            node,
2546            owner_prefix,
2547            owner.global,
2548            &ctx.analyzer,
2549            ctx.visibility,
2550            &ctx.ordinary_type_imports,
2551            ctx.file,
2552            ctx.source,
2553            &parent,
2554            false,
2555            &recovered_scope,
2556        )
2557    } else {
2558        resolve_type_components_lexically_at_for_target_with_scope_cache(
2559            node,
2560            owner_prefix,
2561            owner.global,
2562            &ctx.analyzer,
2563            ctx.visibility,
2564            &ctx.ordinary_type_imports,
2565            ctx.file,
2566            ctx.source,
2567            &parent,
2568            false,
2569            Some(&ctx.lexical_scope_cache),
2570        )
2571    };
2572    let LexicalTypeResolution::Resolved {
2573        unit, candidates, ..
2574    } = resolution
2575    else {
2576        return false;
2577    };
2578    same_member_pointer_owner_identity(&unit, &parent)
2579        || candidates
2580            .iter()
2581            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2582}
2583
2584fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2585    same_visible_symbol(left, right)
2586        || (left.kind() == right.kind()
2587            && left.fq_name() == right.fq_name()
2588            && left.source() == right.source())
2589}
2590
2591/// Resolve a template type nested in a qualified identifier against a concrete
2592/// type target. The target-guided lexical path intentionally applies a
2593/// structured candidate prefilter; that prefilter cannot see a partial
2594/// specialization until the template arguments have selected it. Resolve the
2595/// complete qualified primary first, then apply the parsed arguments and
2596/// retain the result only when it is the requested target.
2597fn resolve_nested_template_type_for_target(
2598    node: Node<'_>,
2599    ctx: &ScanCtx<'_>,
2600) -> Option<LexicalTypeResolution> {
2601    let reference_node = node
2602        .parent()
2603        .filter(|parent| {
2604            parent.kind() == "qualified_identifier"
2605                && parent.child_by_field_name("name") == Some(node)
2606        })
2607        .unwrap_or(node);
2608    let target_resolution = resolve_type_node_lexically_for_target(
2609        reference_node,
2610        &ctx.analyzer,
2611        ctx.visibility,
2612        &ctx.ordinary_type_imports,
2613        ctx.file,
2614        ctx.source,
2615        &ctx.spec.target,
2616        Some(&ctx.lexical_scope_cache),
2617        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2618    );
2619    if let LexicalTypeResolution::Resolved {
2620        unit, candidates, ..
2621    } = target_resolution
2622        && template_reference_candidates_select_target(
2623            reference_node,
2624            &candidates,
2625            &ctx.analyzer,
2626            ctx.visibility,
2627            ctx.file,
2628            ctx.source,
2629            &ctx.spec.target,
2630        )
2631    {
2632        return Some(LexicalTypeResolution::Resolved {
2633            unit,
2634            components: Vec::new(),
2635            candidates,
2636        });
2637    }
2638
2639    let normal_resolution = resolve_type_node_lexically(
2640        reference_node,
2641        &ctx.analyzer,
2642        ctx.visibility,
2643        &ctx.ordinary_type_imports,
2644        ctx.file,
2645        ctx.source,
2646    );
2647    let LexicalTypeResolution::Resolved {
2648        unit,
2649        components,
2650        candidates,
2651    } = normal_resolution
2652    else {
2653        return None;
2654    };
2655    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2656    let specialized = ctx
2657        .visibility
2658        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2659        .ok()
2660        .unwrap_or(unit);
2661    (same_visible_symbol(&specialized, &ctx.spec.target)
2662        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2663    .then_some(LexicalTypeResolution::Resolved {
2664        unit: specialized,
2665        components,
2666        candidates,
2667    })
2668}
2669
2670fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2671    let mut reference = node;
2672    while let Some(parent) = ctx.ancestry.parent(reference) {
2673        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2674            && parent.child_by_field_name("name") == Some(reference)
2675            || matches!(
2676                parent.kind(),
2677                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2678            ) && (parent.child_by_field_name("scope") == Some(reference)
2679                || parent.child_by_field_name("name") == Some(reference));
2680        if !owns_component {
2681            break;
2682        }
2683        reference = parent;
2684    }
2685    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2686        return false;
2687    };
2688    type_reference_component_list_may_name_target(
2689        &components,
2690        global,
2691        &ctx.analyzer,
2692        ctx.visibility,
2693        ctx.file,
2694        &ctx.spec.target,
2695        &ctx.type_reference_component_names,
2696    )
2697}
2698
2699fn type_reference_component_list_may_name_target(
2700    components: &[String],
2701    global: bool,
2702    analyzer: &CppGraphSource<'_>,
2703    visibility: &VisibilityIndex<'_>,
2704    file: &ProjectFile,
2705    target: &CodeUnit,
2706    reference_component_names: &HashSet<String>,
2707) -> bool {
2708    if components.iter().any(|component| {
2709        visibility.type_reference_component_directly_names_target(component, target)
2710    }) {
2711        return true;
2712    }
2713    if components.iter().any(|component| {
2714        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2715    }) {
2716        return true;
2717    }
2718    if components.len() > 1 {
2719        return visibility.qualified_alias_reference_may_reach_target(
2720            analyzer, file, components, global, target,
2721        );
2722    }
2723    components
2724        .iter()
2725        .any(|component| reference_component_names.contains(component))
2726}
2727
2728/// Fast structured admission before a type scan builds its parent and lexical
2729/// indexes. Most inverse candidates merely include the target's header and do
2730/// not contain a reference spelling; the full stateful scan has no work in
2731/// those files. Only maximal grammar type names are tested so a common nested
2732/// alias terminal such as `type` is considered together with its owner.
2733fn file_may_reference_type_target(
2734    root: Node<'_>,
2735    source: &str,
2736    analyzer: &CppGraphSource<'_>,
2737    visibility: &VisibilityIndex<'_>,
2738    file: &ProjectFile,
2739    target: &CodeUnit,
2740    reference_component_names: &HashSet<String>,
2741) -> bool {
2742    visit_file_type_reference_spellings(root, source, |components, global| {
2743        type_reference_component_list_may_name_target(
2744            components,
2745            global,
2746            analyzer,
2747            visibility,
2748            file,
2749            target,
2750            reference_component_names,
2751        )
2752    })
2753}
2754
2755fn visit_file_type_reference_spellings(
2756    root: Node<'_>,
2757    source: &str,
2758    mut visit: impl FnMut(&[String], bool) -> bool,
2759) -> bool {
2760    let mut stopped = false;
2761    walk_named_tree_preorder(root, true, |node| {
2762        if node.kind() == "comment" {
2763            return WalkControl::SkipChildren;
2764        }
2765        if node.kind() == "preproc_arg" {
2766            stopped = object_macro_replacement_type_references(node, source)
2767                .into_iter()
2768                .any(|reference| visit(&reference.components, reference.global));
2769            return if stopped {
2770                WalkControl::Break
2771            } else {
2772                WalkControl::SkipChildren
2773            };
2774        }
2775        if node.kind() == "field_declaration"
2776            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2777        {
2778            let components = [node_text(return_type, source).to_string()];
2779            stopped = visit(&components, false);
2780            if stopped {
2781                return WalkControl::Break;
2782            }
2783        }
2784        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2785            for candidate in [node, type_node] {
2786                let Some((components, global)) = type_reference_components(candidate, source)
2787                else {
2788                    continue;
2789                };
2790                stopped = visit(&components, global);
2791                if stopped {
2792                    return WalkControl::Break;
2793                }
2794            }
2795        }
2796        let Some((components, global)) = type_reference_components(node, source) else {
2797            return WalkControl::Continue;
2798        };
2799        if node.parent().is_some_and(|parent| {
2800            (matches!(parent.kind(), "template_type" | "template_function")
2801                && parent.child_by_field_name("name") == Some(node))
2802                || (matches!(
2803                    parent.kind(),
2804                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2805                ) && (parent.child_by_field_name("scope") == Some(node)
2806                    || parent.child_by_field_name("name") == Some(node)))
2807        }) {
2808            return WalkControl::Continue;
2809        }
2810        stopped = visit(&components, global);
2811        if stopped {
2812            WalkControl::Break
2813        } else {
2814            WalkControl::Continue
2815        }
2816    });
2817    stopped
2818}
2819
2820/// Whether this file decides the reference against the scan target.
2821///
2822/// The last resort of type admission has no structured resolution left: it
2823/// only knows that the spelling mentions the target's name and that the target
2824/// itself is not visible here. That is undecidable when nothing else declares
2825/// the name, but it is a proven negative when the file already makes another
2826/// declaration of the same name visible, because unqualified lookup binds the
2827/// reference to that declaration and the include closure never reaches the
2828/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2829/// the same question before it resolves; asking it again here keeps the
2830/// inverse from admitting a site that forward resolution has already decided
2831/// (#2916).
2832fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2833    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2834        return false;
2835    };
2836    let terminal = components
2837        .last()
2838        .expect("type reference components are non-empty");
2839    if terminal != ctx.spec.target.identifier() {
2840        return false;
2841    }
2842    // A concrete specialization is selected from the template arguments, not
2843    // from the visible primary name, so the name-level candidate set cannot
2844    // decide it. `resolve_type_node_lexically_for_target` makes the same
2845    // exception before its own structured prefilter.
2846    if cpp_template_reference_arguments(node, ctx.source).is_some()
2847        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2848    {
2849        return false;
2850    }
2851    // One C++ type reached through a forward declaration in one header and its
2852    // definition in another is two CodeUnits with different signatures and one
2853    // qualified name. A file that sees either of them sees the target's type,
2854    // so only a candidate under a different qualified name decides the
2855    // reference against it. This is what separates the decoy in an unrelated
2856    // namespace from a redeclaration of the target itself.
2857    if ctx
2858        .visibility
2859        .visible_identifier_candidates(ctx.file, terminal)
2860        .any(|candidate| {
2861            candidate.kind() == ctx.spec.target.kind()
2862                && candidate.fq_name() == ctx.spec.target.fq_name()
2863        })
2864    {
2865        return false;
2866    }
2867    !ctx.visibility
2868        .structured_type_reference_may_resolve_to_target(
2869            &ctx.analyzer,
2870            ctx.file,
2871            std::slice::from_ref(terminal),
2872            false,
2873            &[],
2874            &ctx.spec.target,
2875        )
2876}
2877
2878fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2879    let Some(scope) = node.child_by_field_name("scope") else {
2880        return false;
2881    };
2882    let mut pending = vec![scope];
2883    while let Some(candidate) = pending.pop() {
2884        if candidate.kind() == "template_type" {
2885            return true;
2886        }
2887        if matches!(
2888            candidate.kind(),
2889            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2890        ) {
2891            if let Some(scope) = candidate.child_by_field_name("scope") {
2892                pending.push(scope);
2893            }
2894            if let Some(name) = candidate.child_by_field_name("name") {
2895                pending.push(name);
2896            }
2897        }
2898    }
2899    false
2900}
2901
2902fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2903    ctx.target_group.iter().find(|target| {
2904        same_logical_symbol(target, &ctx.spec.target)
2905            && ctx.visibility.is_physically_visible(ctx.file, target)
2906    })
2907}
2908
2909/// Preserve the terminal token of a resolver-proven alias construction.
2910/// Ordinary qualified types and template aliases retain their full type range.
2911/// Alias identity and the construction context are proven by the resolver.
2912fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
2913    if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
2914        push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
2915    } else {
2916        push_type_hit(type_reference_hit_node(node), ctx);
2917    }
2918}
2919
2920fn resolved_alias_terminal_node<'tree>(
2921    node: Node<'tree>,
2922    candidates: &[CodeUnit],
2923    ctx: &ScanCtx<'_>,
2924) -> Option<Node<'tree>> {
2925    let [candidate] = candidates else {
2926        return None;
2927    };
2928    let alias_provider = ctx.analyzer.type_alias_provider()?;
2929    let candidate_is_alias = alias_provider.is_type_alias(candidate)
2930        && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
2931    let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
2932        && same_visible_symbol(candidate, &ctx.spec.target);
2933    if !(candidate_is_alias || target_is_alias) {
2934        return None;
2935    }
2936    if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
2937        return None;
2938    }
2939    let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
2940        node,
2941        &ctx.spec.target,
2942        &ctx.analyzer,
2943        ctx.visibility,
2944        ctx.file,
2945        ctx.source,
2946    );
2947    if reference
2948        != Some(
2949            crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
2950        )
2951    {
2952        return None;
2953    }
2954    cpp_name_component_nodes(node)?.last().copied()
2955}
2956
2957fn target_guided_missing_direct_temporary_type<'tree>(
2958    function: Node<'tree>,
2959    ctx: &ScanCtx<'_>,
2960) -> Option<Node<'tree>> {
2961    let target = physically_visible_type_target(ctx)?;
2962    let component_nodes = cpp_name_component_nodes(function)?;
2963    let terminal = component_nodes.last().copied()?;
2964    if node_text(terminal, ctx.source) != target.identifier() {
2965        return None;
2966    }
2967    let components = component_nodes
2968        .iter()
2969        .map(|component| node_text(*component, ctx.source).to_string())
2970        .collect::<Vec<_>>();
2971    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
2972    indexed_scope_matches_target_name(
2973        &indexed_scope,
2974        &components,
2975        is_globally_qualified_cpp_name(function),
2976        target,
2977    )
2978    .then_some(terminal)
2979}
2980
2981fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
2982    let Some(function) = call.child_by_field_name("function") else {
2983        return;
2984    };
2985    if !matches!(
2986        function.kind(),
2987        "identifier"
2988            | "type_identifier"
2989            | "template_function"
2990            | "template_type"
2991            | "qualified_identifier"
2992            | "scoped_identifier"
2993            | "scoped_type_identifier"
2994    ) {
2995        return;
2996    }
2997    if !type_reference_components_may_name_target(function, ctx) {
2998        return;
2999    }
3000    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3001        *ctx.raw_match_count += 1;
3002        for scope in scopes {
3003            push_type_hit(scope, ctx);
3004        }
3005        return;
3006    }
3007    let terminal = function_terminal_node(function);
3008    let name = node_text(terminal, ctx.source);
3009    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3010        return;
3011    }
3012    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3013        match resolve_declaring_member_owner(
3014            &ctx.analyzer,
3015            ctx.visibility,
3016            ctx.file,
3017            &enclosing_owner,
3018            name,
3019        ) {
3020            EnclosingMemberOwnerResolution::Owner(owner)
3021                if matches!(
3022                    ctx.visibility
3023                        .visible_member_for_owner_name(ctx.file, &owner, name,),
3024                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3025                ) =>
3026            {
3027                return;
3028            }
3029            EnclosingMemberOwnerResolution::Ambiguous => return,
3030            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3031        }
3032    }
3033
3034    // A type alias used as a direct temporary (`result_type(value)`) is parsed
3035    // as an ordinary identifier call. Callable lookup can be ambiguous when
3036    // parser recovery flattens a namespace or same-spelled aliases are
3037    // visible from sibling distributions. An exact enclosing class owner
3038    // proves the member alias without treating the call as an arbitrary name;
3039    // retain the shadow and declaration-visibility guards above and below.
3040    if ctx
3041        .analyzer
3042        .type_alias_provider()
3043        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3044        && name == ctx.spec.target.identifier()
3045        && physically_visible_type_target(ctx).is_some()
3046        && !local_type_name_shadows(function, ctx)
3047        && type_alias_owner_matches_structured_reference(function, ctx)
3048        && ctx.visibility.external_type_candidate_visible_in_context(
3049            &ctx.analyzer,
3050            ctx.file,
3051            &ctx.spec.target,
3052            function,
3053        )
3054    {
3055        *ctx.raw_match_count += 1;
3056        push_type_hit(terminal, ctx);
3057        return;
3058    }
3059
3060    let call_resolution = resolve_qualified_call_target(
3061        call,
3062        function,
3063        &ctx.analyzer,
3064        ctx.visibility,
3065        &ctx.ordinary_type_imports,
3066        ctx.file,
3067        ctx.source,
3068    );
3069    match call_resolution {
3070        BareCallTargetResolution::Type(unit) => {
3071            if same_visible_symbol(&unit, &ctx.spec.target) {
3072                *ctx.raw_match_count += 1;
3073                push_type_hit(function, ctx);
3074                return;
3075            }
3076        }
3077        BareCallTargetResolution::FreeFunctions(units)
3078            if units.iter().all(|unit| {
3079                unit.fq_name() == ctx.spec.target.fq_name()
3080                    && ctx
3081                        .visibility
3082                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
3083            }) => {}
3084        BareCallTargetResolution::Ambiguous => {
3085            push_unproven_hit(function, ctx);
3086            return;
3087        }
3088        BareCallTargetResolution::FreeFunctions(_)
3089        | BareCallTargetResolution::UnprovenFreeFunctions(_)
3090        | BareCallTargetResolution::CallableShadow => return,
3091        // Generated `.c` includes can leave ordinary callable resolution with
3092        // no active callable even when target-preserving type resolution can
3093        // prove the constructor's class. Let the structured type fallback
3094        // below make that decision.
3095        BareCallTargetResolution::Missing => {}
3096    }
3097    let target_resolution = resolve_type_node_lexically_for_target(
3098        function,
3099        &ctx.analyzer,
3100        ctx.visibility,
3101        &ctx.ordinary_type_imports,
3102        ctx.file,
3103        ctx.source,
3104        &ctx.spec.target,
3105        Some(&ctx.lexical_scope_cache),
3106        ctx.recovered_sentinel_scope(function).as_deref(),
3107    );
3108    match target_resolution {
3109        LexicalTypeResolution::Resolved {
3110            unit, candidates, ..
3111        } if same_visible_symbol(&unit, &ctx.spec.target)
3112            || candidates
3113                .iter()
3114                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3115        {
3116            *ctx.raw_match_count += 1;
3117            push_type_hit(function, ctx);
3118        }
3119        LexicalTypeResolution::Missing => {
3120            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3121                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3122            {
3123                *ctx.raw_match_count += 1;
3124                push_type_hit(hit, ctx);
3125            }
3126        }
3127        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3128    }
3129}
3130
3131pub enum BareCallTargetResolution {
3132    Type(CodeUnit),
3133    FreeFunctions(Vec<CodeUnit>),
3134    UnprovenFreeFunctions(Vec<CodeUnit>),
3135    CallableShadow,
3136    Ambiguous,
3137    Missing,
3138}
3139
3140pub enum BlockUsingCallTargetResolution {
3141    Target(BareCallTargetResolution),
3142    Unindexed(Vec<String>),
3143    Ambiguous,
3144}
3145
3146#[allow(clippy::too_many_arguments)]
3147fn resolve_qualified_call_target(
3148    call: Node<'_>,
3149    function: Node<'_>,
3150    analyzer: &CppGraphSource<'_>,
3151    visibility: &VisibilityIndex,
3152    ordinary_type_imports: &OrdinaryTypeImportCell,
3153    file: &ProjectFile,
3154    source: &str,
3155) -> BareCallTargetResolution {
3156    if matches!(function.kind(), "identifier" | "template_function") {
3157        return resolve_bare_call_target(
3158            call,
3159            function,
3160            analyzer,
3161            visibility,
3162            ordinary_type_imports,
3163            file,
3164            source,
3165        );
3166    }
3167    if !matches!(
3168        function.kind(),
3169        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3170    ) {
3171        return BareCallTargetResolution::Missing;
3172    }
3173    let terminal = function_terminal_node(function);
3174    let name = node_text(terminal, source);
3175    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3176        return BareCallTargetResolution::Missing;
3177    };
3178    components.push(name.to_string());
3179    let qualified_name = components.join("::");
3180    let type_resolution = resolve_type_node_lexically(
3181        function,
3182        analyzer,
3183        visibility,
3184        ordinary_type_imports,
3185        file,
3186        source,
3187    );
3188    let same_name_resolves_to_type = matches!(
3189        &type_resolution,
3190        LexicalTypeResolution::Resolved {
3191            unit,
3192            candidates,
3193            ..
3194        } if cpp_name_for(unit) == qualified_name
3195            || candidates
3196                .iter()
3197                .any(|candidate| cpp_name_for(candidate) == qualified_name)
3198    );
3199    let has_explicit_template_arguments =
3200        cpp_template_reference_arguments(function, source).is_some();
3201    let candidates = visibility
3202        .visible_identifier_candidates(file, name)
3203        .filter(|candidate| {
3204            candidate.is_function()
3205                && type_owner_of(analyzer, candidate).is_none()
3206                && !(same_name_resolves_to_type
3207                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3208                        || has_explicit_template_arguments
3209                            && visibility
3210                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
3211                && cpp_name_for(candidate) == qualified_name
3212                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3213        })
3214        .cloned()
3215        .collect::<Vec<_>>();
3216    if !candidates.is_empty() {
3217        return resolve_callable_candidates(
3218            candidates,
3219            visibility.call_arity_evidence(file, call, source).exact(),
3220            call.start_byte(),
3221            analyzer,
3222            visibility,
3223            file,
3224        );
3225    }
3226    match type_resolution {
3227        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3228        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3229        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3230    }
3231}
3232
3233fn ordinary_binding_lookup_targets(
3234    binding: &OrdinaryTypeImport,
3235    active_bindings: &[&OrdinaryTypeImport],
3236    name: &str,
3237) -> Vec<Vec<String>> {
3238    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3239        return Vec::new();
3240    };
3241    let mut targets = Vec::new();
3242    match binding.target {
3243        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3244        EffectiveUsingTarget::Namespace { .. } => {
3245            let mut stack = vec![qualified.clone()];
3246            let mut visited = HashSet::default();
3247            while let Some(namespace) = stack.pop() {
3248                if !visited.insert(namespace.clone()) {
3249                    continue;
3250                }
3251                let mut target = namespace.clone();
3252                target.push(name.to_string());
3253                targets.push(target);
3254                stack.extend(active_bindings.iter().filter_map(|candidate| {
3255                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3256                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3257                    .then(|| candidate.resolved_target_components.clone())
3258                    .flatten()
3259                }));
3260            }
3261        }
3262    }
3263    targets
3264}
3265
3266fn binding_free_function_candidates(
3267    binding: &OrdinaryTypeImport,
3268    active_bindings: &[&OrdinaryTypeImport],
3269    analyzer: &CppGraphSource<'_>,
3270    visibility: &VisibilityIndex<'_>,
3271    file: &ProjectFile,
3272    name: &str,
3273    reference_byte: usize,
3274) -> Vec<CodeUnit> {
3275    ordinary_binding_lookup_targets(binding, active_bindings, name)
3276        .into_iter()
3277        .flat_map(|target| {
3278            let qualified_name = target.join("::");
3279            visibility
3280                .visible_identifier_candidates(file, name)
3281                .filter(move |candidate| {
3282                    candidate.is_function()
3283                        && type_owner_of(analyzer, candidate).is_none()
3284                        && cpp_name_for(candidate) == qualified_name
3285                        && visibility.declaration_visible_at(
3286                            analyzer,
3287                            file,
3288                            candidate,
3289                            reference_byte,
3290                        )
3291                })
3292                .cloned()
3293        })
3294        .collect()
3295}
3296
3297/// Collapse the candidates unqualified lookup found to one entry per logical
3298/// callable, keeping the first spelling of each.
3299///
3300/// A header declaration and its out-of-line body spell one parameter type
3301/// differently often enough that the persisted signature strings disagree, so
3302/// the string triple alone reports one C++ declaration as an overload set and
3303/// an unproven argument count then turns it into ambiguity.
3304/// `same_logical_callable` answers the string question first and resolves the
3305/// written parameter names only when the strings differ (#2010).
3306fn dedupe_callable_candidates(
3307    candidates: &mut Vec<CodeUnit>,
3308    analyzer: &CppGraphSource<'_>,
3309    visibility: &VisibilityIndex<'_>,
3310) {
3311    let mut deduped = Vec::with_capacity(candidates.len());
3312    for candidate in candidates.drain(..) {
3313        if !deduped
3314            .iter()
3315            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3316        {
3317            deduped.push(candidate);
3318        }
3319    }
3320    *candidates = deduped;
3321}
3322
3323fn resolve_callable_candidates(
3324    candidates: Vec<CodeUnit>,
3325    call_arity: Option<usize>,
3326    reference_byte: usize,
3327    analyzer: &CppGraphSource<'_>,
3328    visibility: &VisibilityIndex<'_>,
3329    file: &ProjectFile,
3330) -> BareCallTargetResolution {
3331    let mut candidates = candidates;
3332    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3333    if candidates.is_empty() {
3334        return BareCallTargetResolution::Missing;
3335    }
3336    let Some(call_arity) = call_arity else {
3337        // An unproven argument count cannot create ambiguity where lookup found
3338        // exactly one name binding: there is nothing to be ambiguous between.
3339        // C has no overloading at all, and a lone C++ candidate is the only
3340        // declaration unqualified lookup reached, so arity cannot pick another
3341        // one (#1811). Keeping it unproven discarded the proven candidate and
3342        // answered `ambiguous` with an empty definition list.
3343        if candidates.len() == 1 {
3344            return BareCallTargetResolution::FreeFunctions(candidates);
3345        }
3346        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3347    };
3348    let applicable = candidates
3349        .into_iter()
3350        .filter(|candidate| {
3351            visibility
3352                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3353                .is_some_and(|arity| arity.accepts(call_arity))
3354        })
3355        .collect::<Vec<_>>();
3356    if applicable.is_empty() {
3357        BareCallTargetResolution::CallableShadow
3358    } else {
3359        BareCallTargetResolution::FreeFunctions(applicable)
3360    }
3361}
3362
3363fn deduplicate_type_import_candidates(
3364    candidates: Vec<(CodeUnit, Vec<String>)>,
3365) -> Vec<(CodeUnit, Vec<String>)> {
3366    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3367    for candidate in candidates {
3368        if !logical
3369            .iter()
3370            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3371        {
3372            logical.push(candidate);
3373        }
3374    }
3375    logical
3376}
3377
3378fn resolve_direct_type_candidates(
3379    candidates: Vec<(CodeUnit, Vec<String>)>,
3380    analyzer: &CppGraphSource<'_>,
3381    visibility: &VisibilityIndex<'_>,
3382    file: &ProjectFile,
3383) -> BareCallTargetResolution {
3384    let logical = deduplicate_type_import_candidates(candidates);
3385    let [(target, components)] = logical.as_slice() else {
3386        return if logical.is_empty() {
3387            BareCallTargetResolution::Missing
3388        } else {
3389            BareCallTargetResolution::Ambiguous
3390        };
3391    };
3392    match visibility
3393        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3394    {
3395        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3396        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3397        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3398    }
3399}
3400
3401/// Resolve a direct using-declaration in the nearest concrete block before
3402/// class-member lookup. A block declaration such as `using std::swap;` adds
3403/// that name to the block scope and hides a same-named member. If the imported
3404/// target is not indexed, retain its structured path as boundary evidence.
3405#[allow(clippy::too_many_arguments)]
3406pub fn resolve_block_using_call_target(
3407    call: Node<'_>,
3408    function: Node<'_>,
3409    analyzer: &CppGraphSource<'_>,
3410    visibility: &VisibilityIndex<'_>,
3411    ordinary_type_imports: &OrdinaryTypeImportCell,
3412    file: &ProjectFile,
3413    source: &str,
3414) -> Option<BlockUsingCallTargetResolution> {
3415    if !matches!(function.kind(), "identifier" | "template_function") {
3416        return None;
3417    }
3418    let name = node_text(function_terminal_node(function), source);
3419    if name.is_empty() {
3420        return None;
3421    }
3422    let bindings = effective_using_bindings_for_name(
3423        visibility,
3424        ordinary_type_imports,
3425        file,
3426        function,
3427        source,
3428        name,
3429    );
3430    let block_bindings = bindings
3431        .iter()
3432        .filter(|binding| {
3433            binding.namespace_scope.is_none()
3434                && binding.block_scope
3435                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3436        })
3437        .collect::<Vec<_>>();
3438    if block_bindings.is_empty() {
3439        return None;
3440    }
3441    let lexical_scope =
3442        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3443            LexicalScopeResolution::Resolved(scope) => scope,
3444            LexicalScopeResolution::Ambiguous => {
3445                return Some(BlockUsingCallTargetResolution::Ambiguous);
3446            }
3447            LexicalScopeResolution::Missing => return None,
3448        };
3449    let reference_guards = preprocessor_guard_environment(function, source);
3450    let active = block_bindings
3451        .into_iter()
3452        .filter(|binding| {
3453            effective_using_binding_active(
3454                binding,
3455                function,
3456                &lexical_scope,
3457                reference_guards.as_ref(),
3458                visibility,
3459                file,
3460            )
3461        })
3462        .collect::<Vec<_>>();
3463    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3464    let at_tier = active
3465        .into_iter()
3466        .filter(|binding| binding.scope_depth == depth)
3467        .collect::<Vec<_>>();
3468    let callable_candidates = at_tier
3469        .iter()
3470        .flat_map(|binding| {
3471            binding_free_function_candidates(
3472                binding,
3473                &[],
3474                analyzer,
3475                visibility,
3476                file,
3477                name,
3478                call.start_byte(),
3479            )
3480        })
3481        .collect::<Vec<_>>();
3482    if !callable_candidates.is_empty() {
3483        return Some(BlockUsingCallTargetResolution::Target(
3484            resolve_callable_candidates(
3485                callable_candidates,
3486                visibility.call_arity_evidence(file, call, source).exact(),
3487                call.start_byte(),
3488                analyzer,
3489                visibility,
3490                file,
3491            ),
3492        ));
3493    }
3494    let type_candidates = at_tier
3495        .iter()
3496        .flat_map(|binding| {
3497            binding_type_candidates(
3498                binding,
3499                &[],
3500                analyzer,
3501                visibility,
3502                file,
3503                name,
3504                None,
3505                call.start_byte(),
3506            )
3507        })
3508        .collect::<Vec<_>>();
3509    if !type_candidates.is_empty() {
3510        return Some(BlockUsingCallTargetResolution::Target(
3511            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3512        ));
3513    }
3514
3515    let mut unindexed = Vec::new();
3516    for binding in at_tier {
3517        let Some(components) = binding.resolved_target_components.as_ref() else {
3518            continue;
3519        };
3520        if !unindexed.contains(components) {
3521            unindexed.push(components.clone());
3522        }
3523    }
3524    match unindexed.as_slice() {
3525        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3526        [] => None,
3527        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3528    }
3529}
3530
3531#[allow(clippy::too_many_arguments)]
3532pub fn resolve_bare_call_target(
3533    call: Node<'_>,
3534    function: Node<'_>,
3535    analyzer: &CppGraphSource<'_>,
3536    visibility: &VisibilityIndex<'_>,
3537    ordinary_type_imports: &OrdinaryTypeImportCell,
3538    file: &ProjectFile,
3539    source: &str,
3540) -> BareCallTargetResolution {
3541    if !matches!(function.kind(), "identifier" | "template_function") {
3542        return BareCallTargetResolution::Missing;
3543    }
3544    let terminal = function_terminal_node(function);
3545    let name = node_text(terminal, source);
3546    if name.is_empty() {
3547        return BareCallTargetResolution::Missing;
3548    }
3549    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3550    let lexical_scope =
3551        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3552            LexicalScopeResolution::Resolved(scope) => scope,
3553            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3554            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3555        };
3556    let type_resolution = resolve_type_node_lexically(
3557        function,
3558        analyzer,
3559        visibility,
3560        ordinary_type_imports,
3561        file,
3562        source,
3563    );
3564    let type_components = match &type_resolution {
3565        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3566        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3567    };
3568    let direct_type_resolution = visibility.resolve_type_components_lexically(
3569        analyzer,
3570        file,
3571        &[name.to_string()],
3572        false,
3573        &lexical_scope,
3574    );
3575    let direct_type_components = match &direct_type_resolution {
3576        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3577        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3578    };
3579    let has_explicit_template_arguments =
3580        cpp_template_reference_arguments(function, source).is_some();
3581    let bindings = effective_using_bindings_for_name(
3582        visibility,
3583        ordinary_type_imports,
3584        file,
3585        function,
3586        source,
3587        name,
3588    );
3589    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3590    // step re-descends from the root (#1927): with no bindings both filters
3591    // below select nothing, so the environment is never consulted.
3592    let function_guards = if bindings.is_empty() {
3593        None
3594    } else {
3595        preprocessor_guard_environment(function, source)
3596    };
3597    let active_bindings = bindings
3598        .iter()
3599        .filter(|binding| {
3600            effective_using_binding_active(
3601                binding,
3602                function,
3603                &lexical_scope,
3604                function_guards.as_ref(),
3605                visibility,
3606                file,
3607            )
3608        })
3609        .collect::<Vec<_>>();
3610    let transitive_bindings = bindings
3611        .iter()
3612        .filter(|binding| {
3613            effective_using_binding_guards_active(
3614                binding,
3615                function.start_byte(),
3616                function_guards.as_ref(),
3617                visibility,
3618                file,
3619            ) && (binding.namespace_scope.is_some()
3620                || (binding.scope_start <= function.start_byte()
3621                    && function.end_byte() <= binding.scope_end))
3622        })
3623        .collect::<Vec<_>>();
3624    let mut concrete_depths = active_bindings
3625        .iter()
3626        .filter(|binding| binding.namespace_scope.is_none())
3627        .map(|binding| binding.scope_depth)
3628        .collect::<Vec<_>>();
3629    concrete_depths.sort_unstable();
3630    concrete_depths.dedup();
3631    for depth in concrete_depths.into_iter().rev() {
3632        let at_tier = active_bindings
3633            .iter()
3634            .copied()
3635            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3636        let direct = at_tier
3637            .clone()
3638            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3639            .flat_map(|binding| {
3640                binding_free_function_candidates(
3641                    binding,
3642                    &transitive_bindings,
3643                    analyzer,
3644                    visibility,
3645                    file,
3646                    name,
3647                    call.start_byte(),
3648                )
3649            })
3650            .collect::<Vec<_>>();
3651        if !direct.is_empty() {
3652            return resolve_callable_candidates(
3653                direct,
3654                call_arity,
3655                call.start_byte(),
3656                analyzer,
3657                visibility,
3658                file,
3659            );
3660        }
3661        let direct_types = at_tier
3662            .clone()
3663            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3664            .flat_map(|binding| {
3665                binding_type_candidates(
3666                    binding,
3667                    &transitive_bindings,
3668                    analyzer,
3669                    visibility,
3670                    file,
3671                    name,
3672                    None,
3673                    call.start_byte(),
3674                )
3675            })
3676            .collect::<Vec<_>>();
3677        if !direct_types.is_empty() {
3678            // `resolve_direct_type_candidates` never consults the argument
3679            // count: it answers the one type the name binds to, or reports the
3680            // competing types. An unknown count therefore cannot make this
3681            // ambiguous (#1812).
3682            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3683        }
3684        let directives = at_tier
3685            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3686            .flat_map(|binding| {
3687                binding_free_function_candidates(
3688                    binding,
3689                    &transitive_bindings,
3690                    analyzer,
3691                    visibility,
3692                    file,
3693                    name,
3694                    call.start_byte(),
3695                )
3696            })
3697            .collect::<Vec<_>>();
3698        if !directives.is_empty() {
3699            return resolve_callable_candidates(
3700                directives,
3701                call_arity,
3702                call.start_byte(),
3703                analyzer,
3704                visibility,
3705                file,
3706            );
3707        }
3708    }
3709    for prefix_len in (0..=lexical_scope.len()).rev() {
3710        let mut qualified = lexical_scope[..prefix_len].to_vec();
3711        qualified.push(name.to_string());
3712        let same_name_resolves_to_type = direct_type_components
3713            .is_some_and(|components| components == qualified.as_slice())
3714            || type_components.is_some_and(|components| components == qualified.as_slice());
3715        let mut direct = visibility
3716            .visible_identifier_candidates(file, name)
3717            .filter(|candidate| {
3718                candidate.is_function()
3719                    && type_owner_of(analyzer, candidate).is_none()
3720                    && !(same_name_resolves_to_type
3721                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3722                            || has_explicit_template_arguments
3723                                && visibility
3724                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3725                    && cpp_name_for(candidate) == qualified.join("::")
3726                    && if analyzer.reference_uses_c_semantics(file) {
3727                        visibility.declaration_visible_for_c_forward_call(
3728                            analyzer,
3729                            file,
3730                            candidate,
3731                            call.start_byte(),
3732                        )
3733                    } else {
3734                        visibility.declaration_visible_at(
3735                            analyzer,
3736                            file,
3737                            candidate,
3738                            call.start_byte(),
3739                        )
3740                    }
3741            })
3742            .cloned()
3743            .collect::<Vec<_>>();
3744        let at_tier = active_bindings.iter().copied().filter(|binding| {
3745            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3746        });
3747        direct.extend(
3748            at_tier
3749                .clone()
3750                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3751                .flat_map(|binding| {
3752                    binding_free_function_candidates(
3753                        binding,
3754                        &transitive_bindings,
3755                        analyzer,
3756                        visibility,
3757                        file,
3758                        name,
3759                        call.start_byte(),
3760                    )
3761                }),
3762        );
3763        if !direct.is_empty() {
3764            return resolve_callable_candidates(
3765                direct,
3766                call_arity,
3767                call.start_byte(),
3768                analyzer,
3769                visibility,
3770                file,
3771            );
3772        }
3773        let mut direct_types = at_tier
3774            .clone()
3775            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3776            .flat_map(|binding| {
3777                binding_type_candidates(
3778                    binding,
3779                    &transitive_bindings,
3780                    analyzer,
3781                    visibility,
3782                    file,
3783                    name,
3784                    None,
3785                    call.start_byte(),
3786                )
3787            })
3788            .collect::<Vec<_>>();
3789        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3790            && let LexicalTypeResolution::Resolved {
3791                unit, components, ..
3792            } = &direct_type_resolution
3793        {
3794            direct_types.push((unit.clone(), components.clone()));
3795        }
3796        if !direct_types.is_empty() {
3797            // `resolve_direct_type_candidates` never consults the argument
3798            // count: it answers the one type the name binds to, or reports the
3799            // competing types. An unknown count therefore cannot make this
3800            // ambiguous (#1812).
3801            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3802        }
3803        let directives = at_tier
3804            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3805            .flat_map(|binding| {
3806                binding_free_function_candidates(
3807                    binding,
3808                    &transitive_bindings,
3809                    analyzer,
3810                    visibility,
3811                    file,
3812                    name,
3813                    call.start_byte(),
3814                )
3815            })
3816            .collect::<Vec<_>>();
3817        if !directives.is_empty() {
3818            return resolve_callable_candidates(
3819                directives,
3820                call_arity,
3821                call.start_byte(),
3822                analyzer,
3823                visibility,
3824                file,
3825            );
3826        }
3827        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3828            // The lexical type resolution below already answers with the single
3829            // type, or with its own ambiguity verdict; the argument count adds
3830            // nothing to that decision (#1812).
3831            return match type_resolution {
3832                LexicalTypeResolution::Resolved { unit, .. } => {
3833                    BareCallTargetResolution::Type(unit)
3834                }
3835                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3836                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3837            };
3838        }
3839    }
3840    // Every lookup tier is exhausted: no callable and no type candidate was
3841    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
3842    // at all, and its early return in get_definition preempted the same-file
3843    // macro fallback - so a call to a macro defined in the referencing file
3844    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
3845    // resolve once an unresolvable include made the argument count unknown.
3846    // A no-candidate outcome is Missing, which is what makes the fallback
3847    // reachable (#1812).
3848    match type_resolution {
3849        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3850        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3851        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3852    }
3853}
3854
3855fn static_qualifier_type_scopes<'tree>(
3856    node: Node<'tree>,
3857    ctx: &ScanCtx<'_>,
3858) -> Option<Vec<Node<'tree>>> {
3859    if !matches!(
3860        node.kind(),
3861        "qualified_identifier" | "scoped_type_identifier"
3862    ) {
3863        return None;
3864    }
3865    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
3866    // this root contains every structured component needed for prefix lookup.
3867    debug_assert!(!is_nested_type_node(node));
3868    let qualified = qualified_owner_components(node, ctx.source)?;
3869    static_qualifier_type_scopes_for_components(node, qualified, ctx)
3870}
3871
3872fn static_qualifier_type_scopes_for_components<'tree>(
3873    node: Node<'tree>,
3874    qualified: QualifiedOwnerComponents<'tree>,
3875    ctx: &ScanCtx<'_>,
3876) -> Option<Vec<Node<'tree>>> {
3877    if !qualified.global
3878        && qualified.names.first().is_some_and(|name| {
3879            name == ctx.spec.target.identifier()
3880                && qualified
3881                    .nodes
3882                    .first()
3883                    .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
3884        })
3885    {
3886        return None;
3887    }
3888    let mut matches = Vec::new();
3889    let mut inherited_injected_name_is_shadowed = false;
3890    for component_count in 1..=qualified.names.len() {
3891        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3892            node,
3893            &qualified.names[..component_count],
3894            qualified.global,
3895            &ctx.analyzer,
3896            ctx.visibility,
3897            &ctx.ordinary_type_imports,
3898            ctx.file,
3899            ctx.source,
3900            &ctx.spec.target,
3901            false,
3902            Some(&ctx.lexical_scope_cache),
3903        );
3904        match resolution {
3905            LexicalTypeResolution::Resolved {
3906                unit, candidates, ..
3907            } if (!ctx
3908                .analyzer
3909                .type_alias_provider()
3910                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3911                || ctx.visibility.external_type_candidate_visible_in_context(
3912                    &ctx.analyzer,
3913                    ctx.file,
3914                    &ctx.spec.target,
3915                    node,
3916                ))
3917                && (same_visible_symbol(&unit, &ctx.spec.target)
3918                    || candidates
3919                        .iter()
3920                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3921                && target_alias_candidates_visible(&candidates, node, ctx) =>
3922            {
3923                let matched =
3924                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3925                if !template_type_component_preserves_target(matched, &candidates, ctx) {
3926                    continue;
3927                }
3928                if !matches.iter().any(|existing: &Node<'_>| {
3929                    existing.start_byte() == matched.start_byte()
3930                        && existing.end_byte() == matched.end_byte()
3931                }) {
3932                    matches.push(matched);
3933                }
3934            }
3935            // The ordinary lexical resolver can remain ambiguous when the
3936            // qualified terminal is an alias whose canonical target is not
3937            // indexed (for example, `Hash::Digest` aliases an external
3938            // `std::array`). The target-guided path below still requires one
3939            // physically visible logical class for every emitted prefix.
3940            LexicalTypeResolution::Ambiguous => {
3941                return (!inherited_injected_name_is_shadowed)
3942                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3943                    .flatten()
3944                    .map(|scope| vec![scope])
3945                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3946            }
3947            LexicalTypeResolution::Resolved { .. } => {
3948                if let Some(matched) =
3949                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3950                {
3951                    matches.push(matched);
3952                }
3953                inherited_injected_name_is_shadowed |= component_count == 1;
3954            }
3955            LexicalTypeResolution::Missing => {
3956                if let Some(matched) =
3957                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3958                {
3959                    matches.push(matched);
3960                }
3961            }
3962        }
3963    }
3964    if matches.is_empty() {
3965        (!inherited_injected_name_is_shadowed)
3966            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3967            .flatten()
3968            .map(|scope| vec![scope])
3969            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3970    } else {
3971        Some(matches)
3972    }
3973}
3974
3975/// Recover a class owner in a qualified expression when guard-aware lookup
3976/// cannot prove the owner. Keep the hit on the owner component, not the member.
3977enum TargetGuidedQualifiedValueOwnerHit<'tree> {
3978    Proven(Node<'tree>),
3979    Unproven(Node<'tree>),
3980}
3981
3982fn target_guided_qualified_value_owner_hit<'tree>(
3983    node: Node<'tree>,
3984    ctx: &ScanCtx<'_>,
3985) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
3986    let target = physically_visible_type_target(ctx)?;
3987    if !target.is_class() {
3988        return None;
3989    }
3990    let qualified = qualified_owner_components(node, ctx.source)?;
3991    let lexical_scope = match enclosing_lexical_scope_components(
3992        node,
3993        &ctx.analyzer,
3994        ctx.visibility,
3995        ctx.file,
3996        ctx.source,
3997    ) {
3998        LexicalScopeResolution::Resolved(scope) => scope,
3999        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4000            enclosing_namespace_components(node, ctx.source)
4001        }
4002    };
4003    let LexicalTypeResolution::Resolved {
4004        unit, candidates, ..
4005    } = ctx.visibility.resolve_type_components_lexically_for_target(
4006        &ctx.analyzer,
4007        ctx.file,
4008        &qualified.names,
4009        qualified.global,
4010        &lexical_scope,
4011        target,
4012    )
4013    else {
4014        return None;
4015    };
4016    let scope = qualified.nodes.last().copied()?;
4017    // Lexical component lookup does not consider source order, so it can
4018    // select a declaration that follows the reference. The forward selection
4019    // proves the owner only when that declaration is visible at the reference.
4020    let forward_uniquely_selects_target = match ctx
4021        .visibility
4022        .resolve_type_components_lexically_for_forward(
4023            &ctx.analyzer,
4024            ctx.file,
4025            &qualified.names,
4026            qualified.global,
4027            &lexical_scope,
4028        ) {
4029        LexicalTypeResolution::Resolved {
4030            unit, candidates, ..
4031        } if candidates.len() == 1 => {
4032            same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4033                && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4034                && ctx.visibility.external_type_candidate_visible_in_context(
4035                    &ctx.analyzer,
4036                    ctx.file,
4037                    &candidates[0],
4038                    scope,
4039                )
4040        }
4041        LexicalTypeResolution::Resolved { .. }
4042        | LexicalTypeResolution::Ambiguous
4043        | LexicalTypeResolution::Missing => false,
4044    };
4045    if forward_uniquely_selects_target
4046        || (same_visible_symbol(&unit, target)
4047            && !candidates.is_empty()
4048            && candidates
4049                .iter()
4050                .all(|candidate| same_visible_symbol(candidate, target)))
4051    {
4052        Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4053    } else if candidates
4054        .iter()
4055        .any(|candidate| same_visible_symbol(candidate, target))
4056    {
4057        Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4058    } else {
4059        None
4060    }
4061}
4062
4063/// Resolve a nested class-owned alias when the indexed alias path is not a
4064/// standalone type candidate. The C++ index stores `basic_json::type_error`
4065/// as a synthetic child of `basic_json`, while source can qualify it through
4066/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4067/// then canonicalize the structured member alias against the requested type.
4068fn target_guided_nested_alias_type_scope<'tree>(
4069    node: Node<'tree>,
4070    qualified: &QualifiedOwnerComponents<'tree>,
4071    component_count: usize,
4072    ctx: &ScanCtx<'_>,
4073) -> Option<Node<'tree>> {
4074    if component_count < 2 {
4075        return None;
4076    }
4077    let (owner_components, member_name) =
4078        qualified.names[..component_count].split_at(component_count - 1);
4079    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4080        node,
4081        owner_components,
4082        qualified.global,
4083        &ctx.analyzer,
4084        ctx.visibility,
4085        &ctx.ordinary_type_imports,
4086        ctx.file,
4087        ctx.source,
4088    ) else {
4089        return None;
4090    };
4091    let member_name = member_name.first()?;
4092    let alias_provider = ctx.analyzer.type_alias_provider()?;
4093    ctx.visibility
4094        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4095        .into_iter()
4096        .filter(|member| alias_provider.is_type_alias(member))
4097        .find(|member| {
4098            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4099                &ctx.analyzer,
4100                ctx.file,
4101                member,
4102                node,
4103            ) || ctx
4104                .visibility
4105                .external_type_candidate_guard_compatible_in_context(
4106                    &ctx.analyzer,
4107                    ctx.file,
4108                    member,
4109                    node,
4110                );
4111            if !member_visible {
4112                return false;
4113            }
4114            same_visible_symbol(member, &ctx.spec.target)
4115                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4116        })
4117        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4118}
4119
4120fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4121    if ctx.visibility.structured_class_alias_resolves_to_target(
4122        &ctx.analyzer,
4123        ctx.file,
4124        candidate,
4125        &ctx.spec.target,
4126    ) {
4127        return ctx.spec.target.clone();
4128    }
4129    let structured = ctx
4130        .visibility
4131        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4132    if let Some(canonical) = structured
4133        .as_ref()
4134        .filter(|canonical| !same_visible_symbol(canonical, candidate))
4135    {
4136        return canonical.clone();
4137    }
4138    structured.unwrap_or_else(|| candidate.clone())
4139}
4140
4141/// Preserve the alias component of a qualified reference when the alias target
4142/// is a dependent nested type and forward lookup retains its primary template
4143/// as the bounded identity.
4144fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4145    ctx.visibility
4146        .external_type_candidate_guard_compatible_in_context(
4147            &ctx.analyzer,
4148            ctx.file,
4149            candidate,
4150            node,
4151        )
4152}
4153
4154fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4155    ctx.visibility.is_physically_visible(ctx.file, candidate)
4156        && class_alias_guard_compatible(candidate, node, ctx)
4157}
4158
4159fn class_alias_path_preserves_target(
4160    candidate: &CodeUnit,
4161    target: &CodeUnit,
4162    ctx: &ScanCtx<'_>,
4163) -> bool {
4164    ctx.visibility.structured_class_alias_path_preserves_target(
4165        &ctx.analyzer,
4166        ctx.file,
4167        candidate,
4168        target,
4169    )
4170}
4171
4172fn target_guided_dependent_alias_qualifier_scope<'tree>(
4173    node: Node<'tree>,
4174    ctx: &ScanCtx<'_>,
4175) -> Option<Node<'tree>> {
4176    if !matches!(
4177        node.kind(),
4178        "qualified_identifier" | "scoped_type_identifier"
4179    ) {
4180        return None;
4181    }
4182    let target = physically_visible_type_target(ctx)?;
4183    let alias_provider = ctx.analyzer.type_alias_provider()?;
4184    if !target.is_class() || alias_provider.is_type_alias(target) {
4185        return None;
4186    }
4187    let nodes = cpp_name_component_nodes(node)?;
4188    let names = nodes
4189        .iter()
4190        .map(|component| node_text(*component, ctx.source).to_string())
4191        .collect::<Vec<_>>();
4192    let global = is_globally_qualified_cpp_name(node);
4193    let lexical_scope = match enclosing_lexical_scope_components(
4194        node,
4195        &ctx.analyzer,
4196        ctx.visibility,
4197        ctx.file,
4198        ctx.source,
4199    ) {
4200        LexicalScopeResolution::Resolved(scope) => scope,
4201        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4202            enclosing_namespace_components(node, ctx.source)
4203        }
4204    };
4205    for component_count in 1..=names.len() {
4206        let components = &names[..component_count];
4207        let name = components.last()?;
4208        let candidates = ctx
4209            .visibility
4210            .visible_identifier_candidates(ctx.file, name)
4211            .filter(|candidate| alias_provider.is_type_alias(candidate))
4212            .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4213            .filter(|candidate| {
4214                let candidate_components = canonical_cpp_scope_components(candidate);
4215                lexical_component_tiers(components, global, &lexical_scope)
4216                    .any(|tier| tier == candidate_components)
4217                    || (component_count == 1
4218                        && member_alias_owner_matches_reference_for(
4219                            candidate,
4220                            nodes[component_count - 1],
4221                            ctx,
4222                        ))
4223            })
4224            .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4225        let mut aliases: Vec<&CodeUnit> = Vec::new();
4226        for candidate in candidates {
4227            if !aliases
4228                .iter()
4229                .any(|existing| same_logical_symbol(existing, candidate))
4230            {
4231                aliases.push(candidate);
4232            }
4233        }
4234        if aliases.len() == 1 {
4235            return nodes.get(component_count - 1).copied();
4236        }
4237        if aliases.len() > 1 {
4238            return None;
4239        }
4240    }
4241    None
4242}
4243
4244/// Preserve an unqualified class-owned alias when its dependent target path
4245/// retains the requested primary template as the bounded forward identity.
4246fn target_guided_dependent_class_alias_leaf<'tree>(
4247    node: Node<'tree>,
4248    ctx: &ScanCtx<'_>,
4249) -> Option<Node<'tree>> {
4250    if node.kind() != "type_identifier"
4251        || is_declaration_name(node)
4252        || local_type_name_shadows(node, ctx)
4253    {
4254        return None;
4255    }
4256    let target = physically_visible_type_target(ctx)?;
4257    let alias_provider = ctx.analyzer.type_alias_provider()?;
4258    if !target.is_class() || alias_provider.is_type_alias(target) {
4259        return None;
4260    }
4261    let name = node_text(node, ctx.source);
4262    let aliases = ctx
4263        .visibility
4264        .visible_identifier_candidates(ctx.file, name)
4265        .filter(|candidate| alias_provider.is_type_alias(candidate))
4266        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4267        .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4268        .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4269        .collect::<Vec<_>>();
4270    matches!(aliases.as_slice(), [_]).then_some(node)
4271}
4272
4273/// Recover a namespace alias whose guard state blocks ordinary visibility.
4274/// Require one visible canonical target and an exact structured alias path.
4275fn target_guided_unproven_alias_type_reference<'tree>(
4276    node: Node<'tree>,
4277    candidates: &[CodeUnit],
4278    ctx: &ScanCtx<'_>,
4279) -> Option<Node<'tree>> {
4280    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4281    let target = physically_visible_type_target(ctx)?;
4282    if !target.is_class() {
4283        return None;
4284    }
4285    let alias_provider = ctx.analyzer.type_alias_provider()?;
4286    let (components, _) = type_reference_components(node, ctx.source)?;
4287    let hit = template_arguments
4288        .as_ref()
4289        .and_then(|_| template_reference_name_node(node))
4290        .map(function_terminal_node)
4291        .unwrap_or_else(|| function_terminal_node(node));
4292    candidates
4293        .iter()
4294        .filter(|candidate| {
4295            alias_provider.is_type_alias(candidate)
4296                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4297                && canonical_cpp_scope_components(candidate) == components
4298        })
4299        .find(|candidate| {
4300            template_arguments.as_ref().map_or_else(
4301                || {
4302                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4303                        || ctx.visibility.structured_alias_primary_preserves_target(
4304                            &ctx.analyzer,
4305                            ctx.file,
4306                            candidate,
4307                            target,
4308                        )
4309                },
4310                |arguments| {
4311                    ctx.visibility.template_alias_arguments_preserve_target(
4312                        &ctx.analyzer,
4313                        ctx.file,
4314                        candidate,
4315                        arguments,
4316                        target,
4317                    )
4318                },
4319            )
4320        })
4321        .map(|_| hit)
4322}
4323
4324fn target_alias_candidates_visible(
4325    candidates: &[CodeUnit],
4326    reference: Node<'_>,
4327    ctx: &ScanCtx<'_>,
4328) -> bool {
4329    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4330        return true;
4331    };
4332    if candidates.iter().any(|candidate| {
4333        !alias_provider.is_type_alias(candidate)
4334            && ctx.visibility.same_template_member_identity(
4335                &ctx.analyzer,
4336                candidate,
4337                &ctx.spec.target,
4338            )
4339    }) {
4340        return true;
4341    }
4342    let target_aliases = candidates
4343        .iter()
4344        .filter(|candidate| {
4345            alias_provider.is_type_alias(candidate)
4346                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4347        })
4348        .collect::<Vec<_>>();
4349    target_aliases.is_empty()
4350        || target_aliases
4351            .iter()
4352            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4353}
4354
4355fn type_candidate_visible_at_reference(
4356    candidate: &CodeUnit,
4357    reference: Node<'_>,
4358    ctx: &ScanCtx<'_>,
4359) -> bool {
4360    let class_owned_alias = ctx
4361        .analyzer
4362        .type_alias_provider()
4363        .is_some_and(|provider| provider.is_type_alias(candidate))
4364        && ctx
4365            .analyzer
4366            .parent_of(candidate)
4367            .is_some_and(|owner| owner.is_class());
4368    if class_owned_alias {
4369        let conditional_family = ctx
4370            .visibility
4371            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4372        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4373            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4374            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4375        let guard_match = ctx
4376            .visibility
4377            .external_type_candidate_guard_compatible_in_context(
4378                &ctx.analyzer,
4379                ctx.file,
4380                candidate,
4381                reference,
4382            );
4383        let general_match = conditional_family
4384            && ctx.visibility.external_type_candidate_visible_in_context(
4385                &ctx.analyzer,
4386                ctx.file,
4387                candidate,
4388                reference,
4389            );
4390        return owner_match && (guard_match || general_match);
4391    }
4392    ctx.visibility.external_type_candidate_visible_in_context(
4393        &ctx.analyzer,
4394        ctx.file,
4395        candidate,
4396        reference,
4397    )
4398}
4399
4400fn unqualified_reference_selects_inherited_alias(
4401    candidate: &CodeUnit,
4402    reference: Node<'_>,
4403    ctx: &ScanCtx<'_>,
4404) -> bool {
4405    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4406        return false;
4407    };
4408    if global || components.len() != 1 {
4409        return false;
4410    }
4411    matches!(
4412        resolve_type_node_lexically_for_target(
4413            reference,
4414            &ctx.analyzer,
4415            ctx.visibility,
4416            &ctx.ordinary_type_imports,
4417            ctx.file,
4418            ctx.source,
4419            candidate,
4420            Some(&ctx.lexical_scope_cache),
4421            ctx.recovered_sentinel_scope(reference).as_deref(),
4422        ),
4423        LexicalTypeResolution::Resolved {
4424            ref unit,
4425            ref candidates,
4426            ..
4427        } if ctx
4428            .visibility
4429            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4430            || candidates.iter().any(|resolved| {
4431                ctx.visibility.same_template_member_identity(
4432                    &ctx.analyzer,
4433                    resolved,
4434                    candidate,
4435                )
4436            })
4437    )
4438}
4439
4440fn qualified_reference_selects_type_candidate(
4441    candidate: &CodeUnit,
4442    reference: Node<'_>,
4443    ctx: &ScanCtx<'_>,
4444) -> bool {
4445    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4446        return false;
4447    };
4448    if components.len() < 2 {
4449        return false;
4450    }
4451    let candidate_components = canonical_cpp_scope_components(candidate);
4452    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4453        match enclosing_lexical_scope_components(
4454            reference,
4455            &ctx.analyzer,
4456            ctx.visibility,
4457            ctx.file,
4458            ctx.source,
4459        ) {
4460            LexicalScopeResolution::Resolved(scope) => scope,
4461            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4462                enclosing_namespace_components(reference, ctx.source)
4463            }
4464        }
4465    });
4466    lexical_component_tiers(&components, global, &lexical_scope)
4467        .any(|qualified| qualified == candidate_components)
4468}
4469
4470fn qualified_type_component_hit_node<'tree>(
4471    component: Node<'tree>,
4472    qualified: Node<'tree>,
4473) -> Node<'tree> {
4474    let mut current = component;
4475    while let Some(parent) = current.parent() {
4476        let is_type_name = matches!(
4477            parent.kind(),
4478            "template_type"
4479                | "qualified_identifier"
4480                | "scoped_identifier"
4481                | "scoped_type_identifier"
4482        ) && parent
4483            .child_by_field_name("name")
4484            .is_some_and(|name| same_node(name, current));
4485        if !is_type_name {
4486            break;
4487        }
4488        current = parent;
4489        if same_node(parent, qualified) {
4490            break;
4491        }
4492    }
4493    current
4494}
4495
4496fn template_type_component_preserves_target(
4497    node: Node<'_>,
4498    candidates: &[CodeUnit],
4499    ctx: &ScanCtx<'_>,
4500) -> bool {
4501    template_reference_candidates_select_target(
4502        node,
4503        candidates,
4504        &ctx.analyzer,
4505        ctx.visibility,
4506        ctx.file,
4507        ctx.source,
4508        &ctx.spec.target,
4509    )
4510}
4511
4512fn template_reference_candidates_select_target(
4513    node: Node<'_>,
4514    candidates: &[CodeUnit],
4515    analyzer: &CppGraphSource<'_>,
4516    visibility: &VisibilityIndex<'_>,
4517    file: &ProjectFile,
4518    source: &str,
4519    target: &CodeUnit,
4520) -> bool {
4521    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4522        return !visibility.is_template_specialization(target);
4523    };
4524    let direct_template_name =
4525        template_reference_name_node(node).map(|name| node_text(name, source));
4526    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4527        analyzer.type_alias_provider().is_some_and(|provider| {
4528            visibility
4529                .visible_identifier_candidates(file, name)
4530                .filter(|candidate| provider.is_type_alias(candidate))
4531                .any(|candidate| {
4532                    visibility.template_alias_arguments_preserve_target(
4533                        analyzer, file, candidate, &arguments, target,
4534                    )
4535                })
4536        })
4537    });
4538    named_alias_selects_target
4539        || candidates.iter().any(|candidate| {
4540            (same_visible_symbol(candidate, target)
4541                && visibility.is_primary_template(target)
4542                && direct_template_name == Some(candidate.identifier()))
4543                || visibility.template_alias_arguments_preserve_target(
4544                    analyzer, file, candidate, &arguments, target,
4545                )
4546                || visibility
4547                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4548                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4549        })
4550}
4551
4552fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4553    let template = if node.kind() == "template_type" {
4554        node
4555    } else {
4556        node.child_by_field_name("name")
4557            .filter(|name| name.kind() == "template_type")?
4558    };
4559    template.child_by_field_name("name")
4560}
4561
4562fn type_resolution_matches_target(
4563    node: Node<'_>,
4564    unit: &CodeUnit,
4565    candidates: &[CodeUnit],
4566    ctx: &ScanCtx<'_>,
4567) -> bool {
4568    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4569}
4570
4571fn type_resolution_matches_unit_target(
4572    node: Node<'_>,
4573    unit: &CodeUnit,
4574    candidates: &[CodeUnit],
4575    target: &CodeUnit,
4576    ctx: &ScanCtx<'_>,
4577) -> bool {
4578    target_alias_candidates_visible(candidates, node, ctx)
4579        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4580}
4581
4582/// The identity half of the type-resolution match, without the alias
4583/// visibility gate.
4584///
4585/// Use it only on the without-visibility fallback path, which reports an
4586/// unproven hit. An alias spelling does not contain the target identifier, so
4587/// the name-mention fallback can never recover a rejected alias reference: the
4588/// site would disappear instead of degrading to a reviewable hit.
4589fn type_resolution_identifies_unit_target(
4590    node: Node<'_>,
4591    unit: &CodeUnit,
4592    candidates: &[CodeUnit],
4593    target: &CodeUnit,
4594    ctx: &ScanCtx<'_>,
4595) -> bool {
4596    if !template_alias_owner_matches_reference(node, target, ctx) {
4597        return false;
4598    }
4599    if ctx.visibility.is_template_specialization(target)
4600        && cpp_template_reference_arguments(node, ctx.source).is_some()
4601    {
4602        let selected_unit =
4603            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4604                ctx.visibility
4605                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4606                    .ok()
4607            });
4608        return selected_unit
4609            .as_ref()
4610            .is_some_and(|selected| same_visible_symbol(selected, target))
4611            || template_reference_candidates_select_target(
4612                node,
4613                candidates,
4614                &ctx.analyzer,
4615                ctx.visibility,
4616                ctx.file,
4617                ctx.source,
4618                target,
4619            );
4620    }
4621    unit == target
4622        || ctx
4623            .visibility
4624            .same_template_member_identity(&ctx.analyzer, unit, target)
4625        || ctx.visibility.c_tag_declaration_family_matches_target(
4626            &ctx.analyzer,
4627            ctx.file,
4628            &candidates.iter().collect::<Vec<_>>(),
4629            target,
4630        )
4631        || ctx.visibility.structured_class_alias_resolves_to_target(
4632            &ctx.analyzer,
4633            ctx.file,
4634            unit,
4635            target,
4636        )
4637        || candidates.iter().any(|candidate| {
4638            ctx.visibility
4639                .same_template_member_identity(&ctx.analyzer, candidate, target)
4640                || ctx.visibility.structured_class_alias_resolves_to_target(
4641                    &ctx.analyzer,
4642                    ctx.file,
4643                    candidate,
4644                    target,
4645                )
4646        })
4647}
4648
4649/// Keep a member alias attached to the class specialization that declares it.
4650/// A target-guided lexical lookup can otherwise retain the primary alias when
4651/// the source reference is inside a partial specialization with the same
4652/// unqualified alias name. Compare the indexed template identities instead of
4653/// rendered text or suffixes.
4654fn template_alias_owner_matches_reference(
4655    node: Node<'_>,
4656    target: &CodeUnit,
4657    ctx: &ScanCtx<'_>,
4658) -> bool {
4659    if !ctx
4660        .analyzer
4661        .type_alias_provider()
4662        .is_some_and(|provider| provider.is_type_alias(target))
4663    {
4664        return true;
4665    }
4666    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4667        return true;
4668    };
4669    if !target_owner.is_class() {
4670        return true;
4671    }
4672    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4673        return true;
4674    };
4675    if !ctx.visibility.is_template_specialization(&target_owner)
4676        && !ctx.visibility.is_template_specialization(&reference_owner)
4677    {
4678        return true;
4679    }
4680    same_visible_symbol(&target_owner, &reference_owner)
4681}
4682
4683fn inherited_injected_class_qualifier_scope<'tree>(
4684    node: Node<'tree>,
4685    ctx: &ScanCtx<'_>,
4686) -> Option<Node<'tree>> {
4687    let qualified = qualified_owner_components(node, ctx.source)?;
4688    if qualified.global || qualified.names.is_empty() {
4689        return None;
4690    }
4691    let injected_name = &qualified.names[0];
4692    if !ctx.spec.target.is_class()
4693        || ctx.spec.target.identifier() != injected_name
4694        || physically_visible_type_target(ctx).is_none()
4695    {
4696        return None;
4697    }
4698    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4699    let owner = ctx.visibility.inherited_injected_class_owner(
4700        &ctx.analyzer,
4701        ctx.file,
4702        &enclosing_owner,
4703        injected_name,
4704    )?;
4705    same_visible_symbol(&owner, &ctx.spec.target)
4706        .then(|| qualified.nodes.first().copied())
4707        .flatten()
4708}
4709
4710/// Resolve each qualified type component against the inverse target while
4711/// preserving C++ lexical-tier precedence and structured alias identity.
4712fn target_guided_qualifier_type_scopes<'tree>(
4713    node: Node<'tree>,
4714    ctx: &ScanCtx<'_>,
4715) -> Option<Vec<Node<'tree>>> {
4716    if !matches!(
4717        node.kind(),
4718        "qualified_identifier" | "scoped_type_identifier"
4719    ) {
4720        return None;
4721    }
4722    let target = physically_visible_type_target(ctx)?;
4723    let qualified = qualified_owner_components(node, ctx.source)?;
4724    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4725    // scope before falling back to suffix recovery.  A short unqualified
4726    // owner can have a same-spelled class in a nested namespace (for example
4727    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4728    // both as possible owners and then fails closed, even though the
4729    // translation unit's lexical scope selects the global class.  Keep the
4730    // suffix path for malformed namespace sentinels, where the parser does
4731    // not expose every indexed scope component.
4732    let lexical_scope = match enclosing_lexical_scope_components(
4733        node,
4734        &ctx.analyzer,
4735        ctx.visibility,
4736        ctx.file,
4737        ctx.source,
4738    ) {
4739        LexicalScopeResolution::Resolved(scope) => scope,
4740        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4741            enclosing_namespace_components(node, ctx.source)
4742        }
4743    };
4744    let indexed_owner_scope =
4745        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4746    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4747    let mut matches = Vec::new();
4748    for component_count in 1..=qualified.names.len() {
4749        let components = &qualified.names[..component_count];
4750        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4751            .collect::<Vec<_>>();
4752        let name = components.last()?;
4753        let mut candidates = Vec::new();
4754        let mut exact_candidates = Vec::new();
4755        for candidate in ctx
4756            .visibility
4757            .visible_identifier_candidates(ctx.file, name)
4758            .filter(|candidate| candidate.is_class())
4759            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4760        {
4761            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4762                brokk_bifrost_core::analyzer::Language::Cpp,
4763                &cpp_name_for(candidate),
4764            );
4765            if !candidate_components.ends_with(components)
4766                || candidates
4767                    .iter()
4768                    .any(|existing| same_logical_symbol(existing, candidate))
4769            {
4770                continue;
4771            }
4772            let exact_lexical_scope = lexical_tiers
4773                .iter()
4774                .any(|expected| expected == &candidate_components);
4775            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4776            let structured_owner_match = indexed_owner_scope
4777                .as_ref()
4778                .is_some_and(|owner| owner.starts_with(candidate_owner))
4779                || recovered_owner_scope
4780                    .as_ref()
4781                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4782            let class_alias_owner_match = ctx
4783                .analyzer
4784                .type_alias_provider()
4785                .is_some_and(|provider| provider.is_type_alias(candidate))
4786                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4787            let macro_namespace_owner_match = is_declaration_name(node)
4788                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4789            if components.len() == 1
4790                && candidate_components != components
4791                && !exact_lexical_scope
4792                && !structured_owner_match
4793                && !class_alias_owner_match
4794                && !macro_namespace_owner_match
4795            {
4796                continue;
4797            }
4798            candidates.push(candidate.clone());
4799            if exact_lexical_scope {
4800                exact_candidates.push(candidate.clone());
4801            }
4802        }
4803        if !exact_candidates.is_empty() {
4804            candidates = exact_candidates;
4805        }
4806        // A typedef spelling can qualify nested C++ members while forward
4807        // lookup canonicalizes that spelling to its underlying class. Preserve
4808        // the exact alias prefix only when structured alias resolution proves
4809        // that it denotes this inverse target.
4810        let canonical_alias_target_matches = matches!(
4811            candidates.as_slice(),
4812            [candidate]
4813                if ctx
4814                    .analyzer
4815                    .type_alias_provider()
4816                    .is_some_and(|provider| provider.is_type_alias(candidate))
4817                    && type_candidate_visible_at_reference(candidate, node, ctx)
4818                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4819                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4820                        brokk_bifrost_core::analyzer::Language::Cpp,
4821                        &cpp_name_for(candidate),
4822                    ) == components
4823                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
4824        );
4825        let direct_alias_target = ctx
4826            .analyzer
4827            .type_alias_provider()
4828            .is_some_and(|provider| provider.is_type_alias(target))
4829            && candidates
4830                .iter()
4831                .any(|candidate| same_symbol(candidate, target));
4832        let unique_target = matches!(
4833            candidates.as_slice(),
4834            [candidate] if same_visible_symbol(candidate, target)
4835        );
4836        if direct_alias_target || unique_target || canonical_alias_target_matches {
4837            let matched = if ctx
4838                .analyzer
4839                .type_alias_provider()
4840                .is_some_and(|provider| provider.is_type_alias(target))
4841                && ctx
4842                    .analyzer
4843                    .parent_of(target)
4844                    .is_some_and(|owner| owner.is_class())
4845                && ctx
4846                    .visibility
4847                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4848                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4849            {
4850                // The alias declaration owns the terminal component. Keep
4851                // the inverse range narrow so `MathLib::bigint` records the
4852                // `bigint` token, not the complete qualified owner path.
4853                qualified.nodes[component_count - 1]
4854            } else {
4855                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4856            };
4857            if template_type_component_preserves_target(matched, &candidates, ctx) {
4858                matches.push(matched);
4859            }
4860        }
4861    }
4862    (!matches.is_empty()).then_some(matches)
4863}
4864
4865fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4866    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4867        return false;
4868    };
4869    ctx.visibility
4870        .visible_identifier_candidates(ctx.file, target.identifier())
4871        .any(|candidate| {
4872            alias_provider.is_type_alias(candidate)
4873                && candidate.identifier() == target.identifier()
4874                && !same_visible_symbol(candidate, target)
4875                && ctx
4876                    .analyzer
4877                    .parent_of(candidate)
4878                    .is_some_and(|owner| owner.is_class())
4879        })
4880}
4881
4882/// Recover an out-of-line owner when the owner declaration and the reference
4883/// use different unknown preprocessor guards. Keep this result unproven.
4884fn target_guided_unproven_out_of_line_owner<'tree>(
4885    node: Node<'tree>,
4886    ctx: &ScanCtx<'_>,
4887) -> Option<Node<'tree>> {
4888    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4889        || !is_declaration_name(node)
4890    {
4891        return None;
4892    }
4893    let target = physically_visible_type_target(ctx)?;
4894    if !target.is_class() {
4895        return None;
4896    }
4897    let qualified = qualified_owner_components(node, ctx.source)?;
4898    let target_components = canonical_cpp_scope_components(target);
4899    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4900    let parser_scope = enclosing_namespace_components(node, ctx.source);
4901    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4902        if !parser_scope.is_empty() || target_namespace.is_empty() {
4903            Some(parser_scope)
4904        } else {
4905            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4906        }
4907    })?;
4908    if has_malformed_wrapper_function_definition_ancestor(node)
4909        && target_namespace.starts_with(&scope)
4910        && target_namespace.len() > scope.len()
4911    {
4912        scope = target_namespace.to_vec();
4913    }
4914    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4915        .any(|components| components == target_components)
4916    {
4917        return None;
4918    }
4919    let owner_name = qualified.names.last()?;
4920    let candidates = ctx
4921        .visibility
4922        .visible_identifier_candidates(ctx.file, owner_name)
4923        .filter(|candidate| {
4924            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4925        })
4926        .collect::<Vec<_>>();
4927    if candidates.is_empty()
4928        || candidates
4929            .iter()
4930            .any(|candidate| !same_visible_symbol(candidate, target))
4931    {
4932        return None;
4933    }
4934    qualified.nodes.last().copied()
4935}
4936
4937fn macro_namespace_scope_matches(
4938    candidate_owner: &[String],
4939    node: Node<'_>,
4940    ctx: &ScanCtx<'_>,
4941) -> bool {
4942    let namespace = enclosing_namespace_components(node, ctx.source);
4943    if namespace.is_empty() || candidate_owner.is_empty() {
4944        return false;
4945    }
4946    let mut expanded_owner = Vec::new();
4947    for component in candidate_owner {
4948        if let Some(replacement) =
4949            ctx.visibility
4950                .object_macro_replacement_at(ctx.file, component, node.start_byte())
4951        {
4952            let replacement_components =
4953                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4954                    brokk_bifrost_core::analyzer::Language::Cpp,
4955                    &replacement,
4956                );
4957            if replacement_components.is_empty() {
4958                return false;
4959            }
4960            expanded_owner.extend(replacement_components);
4961        } else {
4962            expanded_owner.push(component.clone());
4963        }
4964    }
4965    expanded_owner == namespace
4966}
4967
4968fn target_guided_missing_type_leaf<'tree>(
4969    node: Node<'tree>,
4970    ctx: &ScanCtx<'_>,
4971) -> Option<Node<'tree>> {
4972    physically_visible_type_target(ctx)?;
4973    target_guided_missing_dependent_nested_type_leaf(node, ctx)
4974        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
4975        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
4976        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
4977        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
4978        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4979}
4980
4981/// Recover a bare class-owned alias whose structured canonical target is the
4982/// requested type. The class owner must enclose the reference, and every alias
4983/// with that spelling in the owner chain must preserve the same target.
4984fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4985    node: Node<'tree>,
4986    ctx: &ScanCtx<'_>,
4987) -> Option<Node<'tree>> {
4988    if node.kind() != "type_identifier"
4989        || is_declaration_name(node)
4990        || local_type_name_shadows(node, ctx)
4991    {
4992        return None;
4993    }
4994    let alias_provider = ctx.analyzer.type_alias_provider()?;
4995    let name = node_text(node, ctx.source);
4996    let aliases = ctx
4997        .visibility
4998        .visible_identifier_candidates(ctx.file, name)
4999        .filter(|candidate| alias_provider.is_type_alias(candidate))
5000        .filter(|candidate| {
5001            ctx.analyzer
5002                .parent_of(candidate)
5003                .is_some_and(|owner| owner.is_class())
5004        })
5005        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5006        .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5007        .collect::<Vec<_>>();
5008    (!aliases.is_empty()
5009        && aliases.iter().all(|candidate| {
5010            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5011        }))
5012    .then_some(node)
5013}
5014
5015/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5016/// type only when the indexed class owner proves the alias declaration. This
5017/// narrow path covers malformed class bodies without accepting unrelated aliases.
5018fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5019    node: Node<'tree>,
5020    ctx: &ScanCtx<'_>,
5021) -> Option<Node<'tree>> {
5022    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5023        matches!(
5024            declaration.kind(),
5025            "parameter_declaration" | "optional_parameter_declaration"
5026        )
5027    });
5028    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5029        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5030    });
5031    if !parameter && !placement_new_type {
5032        return None;
5033    }
5034    if !ctx
5035        .analyzer
5036        .type_alias_provider()
5037        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5038        || !type_alias_owner_matches_structured_reference(node, ctx)
5039    {
5040        return None;
5041    }
5042    target_guided_missing_declaration_type_leaf(node, ctx)
5043}
5044
5045/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5046/// sentinel prevents ordinary lexical resolution. The indexed target must have
5047/// an indexed class parent, the structured owner path must compose with one
5048/// proven lexical namespace source, and every visible candidate at that exact
5049/// owner path must be the indexed parent. This keeps the fallback owner-based;
5050/// a same-spelled nested type under another template remains unproven.
5051fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5052    node: Node<'tree>,
5053    ctx: &ScanCtx<'_>,
5054) -> Option<Node<'tree>> {
5055    if !matches!(
5056        node.kind(),
5057        "qualified_identifier" | "scoped_type_identifier"
5058    ) || !qualified_type_scope_contains_template(node)
5059    {
5060        return None;
5061    }
5062    let name = node
5063        .child_by_field_name("name")
5064        .filter(|name| name.kind() == "type_identifier")?;
5065    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5066        return None;
5067    }
5068    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5069    if !owner_target.is_class() {
5070        return None;
5071    }
5072    let owner = node.child_by_field_name("scope")?;
5073    let owner_resolution = resolve_type_node_lexically_for_target(
5074        owner,
5075        &ctx.analyzer,
5076        ctx.visibility,
5077        &ctx.ordinary_type_imports,
5078        ctx.file,
5079        ctx.source,
5080        &owner_target,
5081        Some(&ctx.lexical_scope_cache),
5082        ctx.recovered_sentinel_scope(owner).as_deref(),
5083    );
5084    if matches!(
5085        owner_resolution,
5086        LexicalTypeResolution::Resolved {
5087            ref unit,
5088            ref candidates,
5089            ..
5090        } if type_resolution_matches_unit_target(
5091            owner,
5092            unit,
5093            candidates,
5094            &owner_target,
5095            ctx,
5096        )
5097    ) {
5098        return Some(name);
5099    }
5100
5101    let qualified = qualified_owner_components(node, ctx.source)?;
5102    let namespace = ctx
5103        .lexical_scope_cache
5104        .orphaned
5105        .enclosing_namespace_components(node, ctx.source);
5106    let indexed_scope = ctx
5107        .recovered_sentinel_scope(node)
5108        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5109        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5110    let owner_components = canonical_cpp_scope_components(&owner_target);
5111    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5112        .any(|components| components == owner_components)
5113    {
5114        return None;
5115    }
5116    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5117        .into_iter()
5118        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5119        .collect::<Vec<_>>();
5120    (!scoped_candidates.is_empty()
5121        && scoped_candidates
5122            .iter()
5123            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5124    .then_some(name)
5125}
5126
5127/// Recover a nested type-alias reference when parser recovery leaves an
5128/// unqualified template argument under a member function.  The ordinary
5129/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5130/// even though the indexed callable owner proves that the reference is inside
5131/// the class which declares the target alias.
5132fn target_guided_missing_member_alias_type_leaf<'tree>(
5133    node: Node<'tree>,
5134    ctx: &ScanCtx<'_>,
5135) -> Option<Node<'tree>> {
5136    if !is_cpp_template_argument_type_leaf(node)
5137        || is_declaration_name(node)
5138        || ctx
5139            .target_declaration_ranges
5140            .iter()
5141            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5142        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5143        || local_type_name_shadows(node, ctx)
5144        || !ctx
5145            .analyzer
5146            .type_alias_provider()
5147            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5148    {
5149        return None;
5150    }
5151    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5152    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5153    if !owner_scope_matches
5154        && !indexed_scope.is_some_and(|scope| {
5155            indexed_scope_matches_target_name(
5156                &scope,
5157                &[ctx.spec.target.identifier().to_string()],
5158                false,
5159                &ctx.spec.target,
5160            )
5161        })
5162    {
5163        return None;
5164    }
5165    // The indexed symbol table intentionally retains declarations from every
5166    // preprocessor branch and from later source positions.  The recovered
5167    // class-owner scope proves the spelling, but it does not prove that this
5168    // alias was active and introduced before the reference.  Apply the same
5169    // structured guard/source-order check used by the ordinary resolver before
5170    // turning the target-guided recovery into a proven hit.
5171    if !(ctx.visibility.external_type_candidate_visible_in_context(
5172        &ctx.analyzer,
5173        ctx.file,
5174        &ctx.spec.target,
5175        node,
5176    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5177    {
5178        return None;
5179    }
5180    let target_visible = ctx
5181        .visibility
5182        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5183        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5184    target_visible.then_some(node)
5185}
5186
5187/// Recover a class/enum template argument only when the parser's ordinary
5188/// lexical lookup failed but the indexed scope and visible declaration still
5189/// prove the exact target. This intentionally excludes aliases: an alias
5190/// argument needs its own template-argument selection path, while a direct
5191/// class/enum argument can be identified by its canonical scope and symbol.
5192fn target_guided_missing_template_argument_type_leaf<'tree>(
5193    node: Node<'tree>,
5194    ctx: &ScanCtx<'_>,
5195) -> Option<Node<'tree>> {
5196    let target = &ctx.spec.target;
5197    let name = node_text(node, ctx.source);
5198    if !target.is_class()
5199        || !is_cpp_template_argument_type_leaf(node)
5200        || is_declaration_name(node)
5201        || name != target.identifier()
5202        || ctx.local_shadows.is_shadowed(name)
5203        || local_type_name_shadows(node, ctx)
5204        || !ctx.visibility.is_physically_visible(ctx.file, target)
5205        || ctx
5206            .analyzer
5207            .type_alias_provider()
5208            .is_some_and(|provider| provider.is_type_alias(target))
5209    {
5210        return None;
5211    }
5212
5213    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5214    let target_components = canonical_cpp_scope_components(target);
5215    if target_components.last().map(String::as_str) != Some(name)
5216        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5217            .any(|components| components == target_components)
5218    {
5219        return None;
5220    }
5221
5222    // The direct visible class candidate supplies the declaration identity;
5223    // the scope check above supplies its canonical owner path. Do not let an
5224    // alias or a same-scoped competing class enter this recovery path.
5225    let candidates = visible_type_identifier_candidates(ctx, name);
5226    if candidates.is_empty()
5227        || candidates.iter().any(|candidate| {
5228            !candidate.is_class()
5229                || ctx
5230                    .analyzer
5231                    .type_alias_provider()
5232                    .is_some_and(|provider| provider.is_type_alias(candidate))
5233                || (!same_visible_symbol(candidate, target)
5234                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5235                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5236        })
5237        || !candidates
5238            .iter()
5239            .any(|candidate| same_visible_symbol(candidate, target))
5240    {
5241        return None;
5242    }
5243
5244    // Physical visibility covers the file/import projection; this second
5245    // guard preserves declaration ordering and preprocessor branch identity.
5246    ctx.visibility
5247        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5248        .then_some(node)
5249}
5250
5251/// Recover the class owner of an out-of-line member whose trailing attribute
5252/// macro was parsed as a separate function definition around the real body.
5253fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5254    let mut function = node;
5255    while function.kind() != "function_definition" {
5256        function = ctx.ancestry.parent(function)?;
5257    }
5258    let macro_name = function_definition_name_node(function)?;
5259    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5260        return None;
5261    }
5262
5263    // An unknown trailing attribute macro can split one real definition into
5264    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5265    // macro-named function definition that owns the body. Recover only that
5266    // exact CST sequence; a complete declaration or a non-macro function is
5267    // an ordinary independent construct.
5268    let declaration = function.prev_named_sibling()?;
5269    if declaration.kind() != "declaration"
5270        || !declaration.has_error()
5271        || function.start_position().row > declaration.end_position().row + 1
5272    {
5273        return None;
5274    }
5275    let mut missing_semicolon = false;
5276    let mut real_semicolon = false;
5277    for child in children_iter(declaration) {
5278        if child.kind() == ";" {
5279            missing_semicolon |= child.is_missing();
5280            real_semicolon |= !child.is_missing();
5281        }
5282    }
5283    if !missing_semicolon || real_semicolon {
5284        return None;
5285    }
5286
5287    let initializer = declaration.child_by_field_name("declarator")?;
5288    if initializer.kind() != "init_declarator"
5289        || initializer
5290            .child_by_field_name("value")
5291            .is_none_or(|value| value.kind() != "argument_list")
5292    {
5293        return None;
5294    }
5295    let qualified_name = initializer
5296        .child_by_field_name("declarator")
5297        .and_then(declarator_name_node)?;
5298    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5299    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5300    match ctx.visibility.resolve_type_components_lexically(
5301        &ctx.analyzer,
5302        ctx.file,
5303        &qualified.names,
5304        qualified.global,
5305        &lexical_scope,
5306    ) {
5307        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5308        LexicalTypeResolution::Resolved { .. }
5309        | LexicalTypeResolution::Ambiguous
5310        | LexicalTypeResolution::Missing => None,
5311    }
5312}
5313
5314/// A class member alias is visible throughout its complete class scope, even
5315/// when its declaration byte follows a recovered out-of-line member's
5316/// trailing return type. Match the indexed owner path structurally before
5317/// allowing the guard-only visibility check above to waive source ordering.
5318fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5319    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5320}
5321
5322fn member_alias_owner_matches_reference_for(
5323    target: &CodeUnit,
5324    node: Node<'_>,
5325    ctx: &ScanCtx<'_>,
5326) -> bool {
5327    let Some(owner) = ctx.analyzer.parent_of(target) else {
5328        return false;
5329    };
5330    if !owner.is_class() {
5331        return false;
5332    }
5333    let reference_owner = ctx
5334        .class_ranges
5335        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5336        .or_else(|| structured_enclosing_owner(node, ctx));
5337    if reference_owner.as_ref().is_some_and(|reference_owner| {
5338        ctx.visibility
5339            .same_template_owner_identity(&owner, reference_owner)
5340    }) {
5341        return true;
5342    }
5343    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5344        ctx.visibility
5345            .same_template_owner_identity(&owner, &reference_owner)
5346    }) {
5347        return true;
5348    }
5349    if reference_owner.is_some_and(|reference_owner| {
5350        matches!(
5351            resolve_declaring_member_owner(
5352                &ctx.analyzer,
5353                ctx.visibility,
5354                ctx.file,
5355                &reference_owner,
5356                target.identifier(),
5357            ),
5358            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5359                if ctx
5360                    .visibility
5361                    .same_template_owner_identity(&owner, &declaring_owner)
5362        )
5363    }) {
5364        return true;
5365    }
5366    let range = Range {
5367        start_byte: node.start_byte(),
5368        end_byte: node.end_byte(),
5369        start_line: node.start_position().row + 1,
5370        end_line: node.end_position().row + 1,
5371    };
5372    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5373    while let Some(candidate) = indexed_enclosing {
5374        if candidate.is_class()
5375            && ctx
5376                .visibility
5377                .same_template_owner_identity(&owner, &candidate)
5378        {
5379            return true;
5380        }
5381        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5382    }
5383    if let Some(reference_body) = malformed_recovered_class_body(node) {
5384        let mut root = node;
5385        while let Some(parent) = ctx.ancestry.parent(root) {
5386            root = parent;
5387        }
5388        if ctx.analyzer.ranges(target).iter().any(|range| {
5389            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5390                .and_then(malformed_recovered_class_body)
5391                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5392        }) {
5393            return true;
5394        }
5395    }
5396    if structured_enclosing_owner(node, ctx)
5397        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5398    {
5399        return true;
5400    }
5401    let owner_components = canonical_cpp_scope_components(&owner);
5402    if ctx
5403        .recovered_sentinel_scope(node)
5404        .is_some_and(|scope| scope == owner_components)
5405    {
5406        return true;
5407    }
5408    if matches!(
5409        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5410            node,
5411            &ctx.analyzer,
5412            ctx.visibility,
5413            ctx.file,
5414            ctx.source,
5415            false,
5416            false,
5417            Some(&ctx.lexical_scope_cache),
5418        ),
5419        LexicalScopeResolution::Resolved(reference_scope)
5420            if reference_scope == owner_components
5421    ) {
5422        return true;
5423    }
5424    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5425    else {
5426        return false;
5427    };
5428    !owner_components.is_empty() && reference_scope == owner_components
5429}
5430
5431fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5432    loop {
5433        if node.kind() == "compound_statement"
5434            && node
5435                .parent()
5436                .is_some_and(|parent| parent.kind() == "declaration_list")
5437            && node.prev_named_sibling().is_some_and(|header| {
5438                header.kind() == "ERROR"
5439                    && header.end_byte() <= node.start_byte()
5440                    && error_contains_class_header(header)
5441            })
5442        {
5443            return Some(node);
5444        }
5445        node = node.parent()?;
5446    }
5447}
5448
5449/// Whether a shallow parser error contains a recognizable class header.
5450pub fn error_contains_class_header(node: Node<'_>) -> bool {
5451    let mut pending = vec![(node, 0usize)];
5452    while let Some((current, depth)) = pending.pop() {
5453        if matches!(current.kind(), "class" | "struct" | "union") {
5454            let mut sibling = current.next_sibling();
5455            let mut saw_name = false;
5456            while let Some(candidate) = sibling {
5457                match candidate.kind() {
5458                    "comment" => {}
5459                    "{" | "base_class_clause" | ":" => return saw_name,
5460                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5461                    _ if !candidate.is_named() => {}
5462                    _ => break,
5463                }
5464                sibling = candidate.next_sibling();
5465            }
5466        }
5467        if depth >= 1 {
5468            continue;
5469        }
5470        let mut cursor = current.walk();
5471        pending.extend(
5472            current
5473                .children(&mut cursor)
5474                .filter(|child| child.kind() != "compound_statement")
5475                .map(|child| (child, depth + 1)),
5476        );
5477    }
5478    false
5479}
5480
5481fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5482    has_ancestor_kind(node, "compound_statement")
5483        && ctx
5484            .visibility
5485            .external_type_candidate_guard_compatible_in_context(
5486                &ctx.analyzer,
5487                ctx.file,
5488                &ctx.spec.target,
5489                node,
5490            )
5491}
5492
5493fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5494    ctx.analyzer
5495        .type_alias_provider()
5496        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5497        && member_alias_owner_matches_reference(node, ctx)
5498}
5499
5500/// A nested class can use aliases declared by any enclosing class. Preserve
5501/// that structured owner chain for malformed macro-return nodes, whose phantom
5502/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5503fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5504    if !ctx
5505        .analyzer
5506        .type_alias_provider()
5507        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5508    {
5509        return false;
5510    }
5511    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5512        return false;
5513    };
5514    let mut reference_owner = structured_enclosing_owner(node, ctx);
5515    while let Some(owner) = reference_owner {
5516        if same_logical_symbol(&target_owner, &owner) {
5517            return true;
5518        }
5519        reference_owner = ctx.analyzer.parent_of(&owner);
5520    }
5521    false
5522}
5523
5524/// An enclosing class alias is only usable when no nearer class declares the
5525/// same type name. The recovered macro-return path does not have a complete
5526/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5527/// rule before the enclosing alias fast path runs.
5528fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5529    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5530        return false;
5531    };
5532    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5533        return false;
5534    };
5535    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5536        return false;
5537    };
5538    let candidates = ctx
5539        .visibility
5540        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5541        .filter(|candidate| {
5542            candidate.is_class()
5543                && alias_provider.is_type_alias(candidate)
5544                && !same_visible_symbol(candidate, &ctx.spec.target)
5545        })
5546        .cloned()
5547        .collect::<Vec<_>>();
5548
5549    let mut owner = Some(reference_owner);
5550    while let Some(owner_unit) = owner {
5551        if same_logical_symbol(&target_owner, &owner_unit) {
5552            return false;
5553        }
5554        if candidates.iter().any(|candidate| {
5555            ctx.analyzer
5556                .parent_of(candidate)
5557                .is_some_and(|candidate_owner| {
5558                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5559                })
5560                && ctx
5561                    .visibility
5562                    .external_type_candidate_guard_compatible_in_context(
5563                        &ctx.analyzer,
5564                        ctx.file,
5565                        candidate,
5566                        node,
5567                    )
5568        }) {
5569            return true;
5570        }
5571        owner = ctx.analyzer.parent_of(&owner_unit);
5572    }
5573    false
5574}
5575
5576fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5577    if cpp_active_template_type_parameter(
5578        node,
5579        ctx.spec.target.identifier(),
5580        ctx.source,
5581        &ctx.ancestry,
5582    ) {
5583        return true;
5584    }
5585    let Some(callable) = nearest_callable_scope(node) else {
5586        return false;
5587    };
5588    let mut root_callable = callable;
5589    let mut ancestor = ctx.ancestry.parent(callable);
5590    while let Some(current) = ancestor {
5591        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5592            root_callable = current;
5593        }
5594        ancestor = ctx.ancestry.parent(current);
5595    }
5596
5597    let mut stack = vec![root_callable];
5598    while let Some(current) = stack.pop() {
5599        if current.start_byte() >= node.start_byte() {
5600            continue;
5601        }
5602        if let Some(name) = local_type_name_declaration_node(current)
5603            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5604            && nearest_callable_scope(current).is_some_and(|owner| {
5605                !is_malformed_wrapper_function_definition(owner)
5606                    && owner.start_byte() <= callable.start_byte()
5607                    && callable.end_byte() <= owner.end_byte()
5608            })
5609            && local_alias_scope_contains_node(current, node)
5610        {
5611            return true;
5612        }
5613        let mut cursor = current.walk();
5614        stack.extend(current.named_children(&mut cursor));
5615    }
5616    false
5617}
5618
5619fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5620    local_type_alias_name_node(node).or_else(|| match node.kind() {
5621        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5622            .child_by_field_name("name")
5623            .filter(|name| is_declaration_name(*name)),
5624        _ => None,
5625    })
5626}
5627
5628fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5629    loop {
5630        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5631            return Some(node);
5632        }
5633        node = node.parent()?;
5634    }
5635}
5636
5637fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5638    match node.kind() {
5639        "alias_declaration" => node.child_by_field_name("name"),
5640        "type_definition" => node
5641            .child_by_field_name("declarator")
5642            .and_then(declarator_name_node),
5643        _ => None,
5644    }
5645}
5646
5647fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5648    let mut current = alias.parent();
5649    while let Some(parent) = current {
5650        if matches!(
5651            parent.kind(),
5652            "class_specifier" | "struct_specifier" | "union_specifier"
5653        ) {
5654            return false;
5655        }
5656        if parent.kind() == "compound_statement" {
5657            return parent.start_byte() <= node.start_byte()
5658                && node.end_byte() <= parent.end_byte();
5659        }
5660        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5661            let Some(body) = parent.child_by_field_name("body") else {
5662                return false;
5663            };
5664            return node_is_within(body, alias) && node_is_within(body, node);
5665        }
5666        current = parent.parent();
5667    }
5668    false
5669}
5670
5671fn target_guided_missing_declaration_type_leaf<'tree>(
5672    node: Node<'tree>,
5673    ctx: &ScanCtx<'_>,
5674) -> Option<Node<'tree>> {
5675    if is_declaration_name(node) {
5676        return None;
5677    }
5678    let component_nodes = cpp_name_component_nodes(node)?;
5679    let name_node = component_nodes.last().copied()?;
5680    let name = node_text(name_node, ctx.source);
5681    if name != ctx.spec.target.identifier() {
5682        return None;
5683    }
5684    let inside_target_declaration = ctx
5685        .target_declaration_ranges
5686        .iter()
5687        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5688    if !inside_target_declaration
5689        && !ctx.visibility.external_type_candidate_visible_in_context(
5690            &ctx.analyzer,
5691            ctx.file,
5692            &ctx.spec.target,
5693            node,
5694        )
5695    {
5696        return None;
5697    }
5698    let components = component_nodes
5699        .iter()
5700        .map(|component| node_text(*component, ctx.source).to_string())
5701        .collect::<Vec<_>>();
5702    let local_alias_shadow = local_type_name_shadows(node, ctx);
5703    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5704    let indexed_alias_owner = ctx
5705        .analyzer
5706        .type_alias_provider()
5707        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5708        && member_alias_owner_matches_reference(node, ctx);
5709    let target_alias_self_reference = inside_target_declaration
5710        && ctx
5711            .analyzer
5712            .type_alias_provider()
5713            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5714    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5715        &ctx.analyzer,
5716        ctx.file,
5717        &ctx.spec.target,
5718        node,
5719    ) || member_alias_complete_class_context(node, ctx);
5720    if !target_alias_self_reference
5721        && !local_alias_shadow
5722        && member_alias_visible
5723        && (structured_alias_owner || indexed_alias_owner)
5724    {
5725        return Some(node);
5726    }
5727    let declaration = nearest_declaration_type_context(node)?;
5728    let candidates = visible_type_identifier_candidates(ctx, name);
5729    let unique_visible_target = !candidates.is_empty()
5730        && candidates
5731            .iter()
5732            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5733    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5734    let exact_scope_match = indexed_scope_matches_target_name(
5735        &indexed_scope,
5736        &components,
5737        is_globally_qualified_cpp_name(node),
5738        &ctx.spec.target,
5739    );
5740    if matches!(declaration.kind(), "field_declaration" | "declaration") {
5741        let parser_lost_declaration_scope =
5742            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5743                && unique_visible_target;
5744        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5745    }
5746    let lost_namespace_parameter_context =
5747        matches!(
5748            declaration.kind(),
5749            "parameter_declaration" | "optional_parameter_declaration"
5750        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5751    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5752        return Some(node);
5753    }
5754    None
5755}
5756
5757fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5758    node: Node<'tree>,
5759    ctx: &ScanCtx<'_>,
5760) -> Option<Node<'tree>> {
5761    let mut stack = vec![node];
5762    while let Some(candidate) = stack.pop() {
5763        if candidate.kind() == "type_identifier"
5764            && !is_declaration_name(candidate)
5765            && matches!(
5766                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
5767                Some("template_type")
5768            )
5769        {
5770            let mut current = ctx.ancestry.parent(candidate);
5771            let mut saw_qualified = false;
5772            let mut saw_dependent = false;
5773            let mut saw_type_descriptor = false;
5774            let mut saw_alias_declaration = false;
5775            while let Some(ancestor) = current {
5776                match ancestor.kind() {
5777                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5778                    "dependent_type" => saw_dependent = true,
5779                    "type_descriptor" => saw_type_descriptor = true,
5780                    "alias_declaration" => {
5781                        saw_alias_declaration = true;
5782                        break;
5783                    }
5784                    "template_type"
5785                    | "template_argument_list"
5786                    | "typename"
5787                    | "template_declaration" => {}
5788                    _ => {}
5789                }
5790                current = ctx.ancestry.parent(ancestor);
5791            }
5792            let name = node_text(candidate, ctx.source);
5793            let visible_candidates = visible_type_identifier_candidates(ctx, name);
5794            let canonical_alias_target = visible_candidates
5795                .iter()
5796                .filter_map(|alias| ctx.visibility.alias_target(alias))
5797                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5798            let alias_resolves =
5799                ctx.visibility
5800                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
5801                    || canonical_alias_target;
5802            if saw_qualified
5803                && saw_dependent
5804                && saw_type_descriptor
5805                && saw_alias_declaration
5806                && alias_resolves
5807                && ctx.visibility.external_type_candidate_visible_in_context(
5808                    &ctx.analyzer,
5809                    ctx.file,
5810                    &ctx.spec.target,
5811                    candidate,
5812                )
5813            {
5814                return Some(candidate);
5815            }
5816        }
5817        push_named_children_reversed(candidate, &mut stack);
5818    }
5819    None
5820}
5821
5822fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5823    let mut current = Some(node);
5824    while let Some(ancestor) = current {
5825        if matches!(
5826            ancestor.kind(),
5827            "field_declaration"
5828                | "parameter_declaration"
5829                | "optional_parameter_declaration"
5830                | "declaration"
5831                | "type_descriptor"
5832        ) {
5833            let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
5834            if contains_type
5835                && !(ancestor.kind() == "type_descriptor"
5836                    && is_cpp_template_argument_type_leaf(node))
5837            {
5838                return Some(ancestor);
5839            }
5840            if ancestor.kind() == "type_descriptor"
5841                && ancestor.parent().is_some_and(|parent| {
5842                    matches!(
5843                        parent.kind(),
5844                        "cast_expression"
5845                            | "new_expression"
5846                            | "sizeof_expression"
5847                            | "alignof_expression"
5848                            | "typeid_expression"
5849                    )
5850                })
5851            {
5852                return Some(ancestor);
5853            }
5854        }
5855        if matches!(
5856            ancestor.kind(),
5857            "compound_statement"
5858                | "translation_unit"
5859                | "namespace_definition"
5860                | "alias_declaration"
5861                | "type_definition"
5862                | "base_class_clause"
5863        ) {
5864            return None;
5865        }
5866        current = ancestor.parent();
5867    }
5868    None
5869}
5870
5871fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5872    let mut candidates = Vec::new();
5873    for candidate in ctx
5874        .visibility
5875        .visible_identifier_candidates(ctx.file, name)
5876        .filter(|candidate| {
5877            candidate.is_class()
5878                || ctx
5879                    .analyzer
5880                    .type_alias_provider()
5881                    .is_some_and(|provider| provider.is_type_alias(candidate))
5882        })
5883    {
5884        if !candidates
5885            .iter()
5886            .any(|existing| same_logical_symbol(existing, candidate))
5887        {
5888            candidates.push(candidate.clone());
5889        }
5890    }
5891    candidates
5892}
5893
5894/// Recover a direct type-alias argument of `static_cast` when parser recovery
5895/// misclassifies a namespace alias as a local declaration. The indexed scope
5896/// and exact alias identity are required so a same-spelled alias in another
5897/// namespace remains excluded.
5898fn target_guided_static_cast_alias_type_descriptor<'tree>(
5899    node: Node<'tree>,
5900    ctx: &ScanCtx<'_>,
5901) -> Option<Node<'tree>> {
5902    if node.kind() != "type_descriptor" {
5903        return None;
5904    }
5905    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
5906        parent.kind() == "template_argument_list"
5907            && parent.named_child_count() == 1
5908            && parent.named_child(0) == Some(node)
5909    })?;
5910    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
5911        parent.kind() == "template_function"
5912            && parent.child_by_field_name("arguments") == Some(argument_list)
5913    })?;
5914    let name = template.child_by_field_name("name")?;
5915    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5916        return None;
5917    }
5918    let target = &ctx.spec.target;
5919    if node_text(node, ctx.source) != target.identifier()
5920        || !ctx
5921            .analyzer
5922            .type_alias_provider()
5923            .is_some_and(|provider| provider.is_type_alias(target))
5924        || !ctx.visibility.is_physically_visible(ctx.file, target)
5925        || !ctx.visibility.external_type_candidate_visible_in_context(
5926            &ctx.analyzer,
5927            ctx.file,
5928            target,
5929            node,
5930        )
5931    {
5932        return None;
5933    }
5934
5935    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5936    let target_scope = canonical_cpp_scope_components(target);
5937    let name_components = [target.identifier().to_string()];
5938    if !lexical_component_tiers(&name_components, false, &indexed_scope)
5939        .any(|components| components == target_scope)
5940    {
5941        return None;
5942    }
5943
5944    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5945    if !candidates
5946        .iter()
5947        .any(|candidate| same_visible_symbol(candidate, target))
5948    {
5949        return None;
5950    }
5951    if candidates.iter().any(|candidate| {
5952        !same_visible_symbol(candidate, target)
5953            && lexical_component_tiers(&name_components, false, &indexed_scope)
5954                .any(|components| components == canonical_cpp_scope_components(candidate))
5955    }) {
5956        return None;
5957    }
5958    Some(node)
5959}
5960
5961fn indexed_scope_matches_target_name(
5962    indexed_scope: &[String],
5963    components: &[String],
5964    global: bool,
5965    target: &CodeUnit,
5966) -> bool {
5967    let target_name = cpp_name_for(target);
5968    lexical_component_tiers(components, global, indexed_scope)
5969        .any(|qualified| qualified.join("::") == target_name)
5970}
5971
5972fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5973    // A global scope or one surviving name alone cannot distinguish a lost
5974    // namespace from a real global/unrelated reference. Require an enclosing
5975    // declaration component as well as a namespace component matching the
5976    // target's qualified scope.
5977    if target.package_name().is_empty() || indexed_scope.len() <= 1 {
5978        return false;
5979    }
5980    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5981        brokk_bifrost_core::analyzer::Language::Cpp,
5982        &cpp_name_for(target),
5983    );
5984    target_scope.pop();
5985    (1..indexed_scope.len())
5986        .rev()
5987        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5988}
5989
5990fn indexed_enclosing_lexical_scope(
5991    analyzer: &CppGraphSource<'_>,
5992    file: &ProjectFile,
5993    node: Node<'_>,
5994) -> Option<Vec<String>> {
5995    let range = Range {
5996        start_byte: node.start_byte(),
5997        end_byte: node.end_byte(),
5998        start_line: node.start_position().row,
5999        end_line: node.end_position().row,
6000    };
6001    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6002    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6003        brokk_bifrost_core::analyzer::Language::Cpp,
6004        &cpp_name_for(&enclosing),
6005    );
6006    if !enclosing.is_class() && !enclosing.is_module() {
6007        components.pop();
6008    }
6009    Some(components)
6010}
6011
6012fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6013    if node.kind() != "qualified_identifier" {
6014        return None;
6015    }
6016    let mut stack = vec![node];
6017    while let Some(current) = stack.pop() {
6018        if current.kind() != "qualified_identifier" {
6019            continue;
6020        }
6021        if let Some(scope) = current.child_by_field_name("scope") {
6022            let text = qualified_scope_text(scope, ctx.source);
6023            if name_mentions(&text, &ctx.spec.member_name) {
6024                return Some(scope);
6025            }
6026        }
6027        let mut cursor = current.walk();
6028        for child in current.named_children(&mut cursor) {
6029            if child.kind() == "qualified_identifier" {
6030                stack.push(child);
6031            }
6032        }
6033    }
6034    None
6035}
6036
6037fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6038    let mut parts = vec![node_text(scope, source).to_string()];
6039    let mut current = scope.parent();
6040    while let Some(qualified) = current {
6041        let Some(parent) = qualified.parent() else {
6042            break;
6043        };
6044        if parent.kind() != "qualified_identifier"
6045            || parent.child_by_field_name("name") != Some(qualified)
6046        {
6047            break;
6048        }
6049        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6050            parts.push(node_text(outer_scope, source).to_string());
6051        }
6052        current = Some(parent);
6053    }
6054    parts.reverse();
6055    parts.join("::")
6056}
6057
6058fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6059    if node.kind() == "using_declaration" {
6060        maybe_record_using_callable_hit(node, ctx);
6061        return;
6062    }
6063    if has_ancestor_kind(node, "using_declaration") {
6064        return;
6065    }
6066    if node.kind() == "function_definition" {
6067        return;
6068    }
6069    if !matches!(
6070        node.kind(),
6071        "call_expression"
6072            | "new_expression"
6073            | "compound_literal_expression"
6074            | "declaration"
6075            | "field_initializer"
6076    ) {
6077        return;
6078    }
6079    let Some(owner) = ctx.spec.owner.as_ref() else {
6080        return;
6081    };
6082    if node.kind() == "field_initializer" {
6083        if !field_initializer_constructs_target(node, ctx, owner)
6084            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6085        {
6086            return;
6087        }
6088        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6089            match ctx
6090                .visibility
6091                .call_arity_evidence(ctx.file, node, ctx.source)
6092                .accepts(expected)
6093            {
6094                Some(true) => {}
6095                Some(false) => return,
6096                None => {
6097                    push_unproven_hit(node, ctx);
6098                    return;
6099                }
6100            }
6101        }
6102        match constructor_overload_selection(node, ctx) {
6103            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6104            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6105            ConstructorOverloadSelection::OtherOverload => {}
6106        }
6107        return;
6108    }
6109    if node.kind() == "declaration" {
6110        if declaration_is_object_construction_candidate(node, ctx)
6111            && declaration_mentions_type(node, ctx, owner)
6112            && ctx
6113                .spec
6114                .callable_arity_at(node.start_byte())
6115                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6116        {
6117            match constructor_overload_selection(node, ctx) {
6118                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6119                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6120                ConstructorOverloadSelection::OtherOverload => {}
6121            }
6122        }
6123        return;
6124    }
6125    let Some(type_node) = constructor_type_node(node) else {
6126        return;
6127    };
6128    let hit_node = function_terminal_node(type_node);
6129    let text = node_text(type_node, ctx.source);
6130    if !name_mentions(text, &ctx.spec.member_name) {
6131        return;
6132    }
6133    *ctx.raw_match_count += 1;
6134    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6135        match ctx
6136            .visibility
6137            .call_arity_evidence(ctx.file, node, ctx.source)
6138            .accepts(expected)
6139        {
6140            Some(true) => {}
6141            Some(false) => return,
6142            None => {
6143                push_unproven_hit(hit_node, ctx);
6144                return;
6145            }
6146        }
6147    }
6148    match constructor_overload_selection(node, ctx) {
6149        ConstructorOverloadSelection::Target => {}
6150        ConstructorOverloadSelection::Ambiguous => {
6151            push_unproven_hit(hit_node, ctx);
6152            return;
6153        }
6154        ConstructorOverloadSelection::OtherOverload => return,
6155    }
6156    let structured_resolution = resolve_type_node_lexically_for_target(
6157        type_node,
6158        &ctx.analyzer,
6159        ctx.visibility,
6160        &ctx.ordinary_type_imports,
6161        ctx.file,
6162        ctx.source,
6163        owner,
6164        Some(&ctx.lexical_scope_cache),
6165        ctx.recovered_sentinel_scope(type_node).as_deref(),
6166    );
6167    let structurally_resolves = matches!(
6168        &structured_resolution,
6169        LexicalTypeResolution::Resolved {
6170            unit, candidates, ..
6171        } if same_visible_symbol(unit, owner)
6172            || candidates
6173                .iter()
6174                .any(|candidate| same_visible_symbol(candidate, owner))
6175    );
6176    if structurally_resolves
6177        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6178            && ctx
6179                .visibility
6180                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6181    {
6182        push_hit(hit_node, ctx);
6183    } else {
6184        push_unproven_hit(hit_node, ctx);
6185    }
6186}
6187
6188/// What the overload filter says about a construction site relative to the
6189/// constructor being scanned.
6190enum ConstructorOverloadSelection {
6191    /// The arguments leave the scan target as the only viable constructor, or
6192    /// the filter has no evidence to apply here. Either way nothing about the
6193    /// arguments argues against the site, so the rest of the scan decides it.
6194    Target,
6195    /// More than one of the owner's constructors stays viable, so the site
6196    /// cannot be proven to belong to the target.
6197    Ambiguous,
6198    /// The arguments select a sibling constructor and not the target.
6199    OtherOverload,
6200}
6201
6202/// Which of the owner's constructors the arguments at a construction site
6203/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6204/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6205/// `T var(args)` declaration.
6206///
6207/// This is the inverse reading of the forward direction's overload choice. The
6208/// candidate set is the owner's arity-compatible constructors, the arguments are
6209/// typed by the same `expression_arg_type` the method and free-function scan
6210/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6211/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6212/// arity alone made a scan of one overload claim every same-arity sibling's call
6213/// (#2908).
6214fn constructor_overload_selection(
6215    node: Node<'_>,
6216    ctx: &ScanCtx<'_>,
6217) -> ConstructorOverloadSelection {
6218    let Some(owner) = ctx.spec.owner.as_ref() else {
6219        return ConstructorOverloadSelection::Target;
6220    };
6221    if ctx.spec.param_types.is_none() {
6222        return ConstructorOverloadSelection::Target;
6223    }
6224    // A `T var(args)` declaration keeps its arguments under the declarator or the
6225    // init declarator's value; every other shape spells an argument list that the
6226    // shared call helpers find, and its argument count needs the macro-aware
6227    // arity evidence.
6228    let (arity, arg_types) = if node.kind() == "declaration" {
6229        match declaration_constructor_initializer(node) {
6230            DeclarationConstructorInitializer::Arguments(arguments) => (
6231                argument_children(arguments).count(),
6232                argument_list_types(arguments, ctx),
6233            ),
6234            DeclarationConstructorInitializer::Expression(value) => {
6235                (1, vec![expression_arg_type(value, ctx)])
6236            }
6237            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6238        }
6239    } else {
6240        let Some(arity) = ctx
6241            .visibility
6242            .call_arity_evidence(ctx.file, node, ctx.source)
6243            .exact()
6244        else {
6245            return ConstructorOverloadSelection::Target;
6246        };
6247        (arity, call_argument_types(node, ctx))
6248    };
6249    let mut candidates = ctx
6250        .visibility
6251        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6252        .into_iter()
6253        .filter(|unit| unit.is_function())
6254        .cloned()
6255        .collect::<Vec<_>>();
6256    candidates.retain(|unit| {
6257        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6258    });
6259    if !candidates
6260        .iter()
6261        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6262    {
6263        return ConstructorOverloadSelection::Target;
6264    }
6265    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6266        candidates,
6267        &arg_types,
6268        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6269        &|candidate| {
6270            ctx.visibility
6271                .callable_is_template_declaration(&ctx.analyzer, candidate)
6272        },
6273        &|name| ctx.visibility.resolve_type(ctx.file, name),
6274        &|left, right| same_visible_symbol(left, right),
6275    );
6276    if !filtered
6277        .iter()
6278        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6279    {
6280        return ConstructorOverloadSelection::OtherOverload;
6281    }
6282    // A header declaration and its out-of-line body are one constructor even
6283    // when their persisted signature strings spell a parameter differently, so
6284    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6285    if filtered.iter().all(|candidate| {
6286        ctx.visibility
6287            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6288    }) {
6289        ConstructorOverloadSelection::Target
6290    } else {
6291        ConstructorOverloadSelection::Ambiguous
6292    }
6293}
6294
6295fn unqualified_base_initializer_constructs_target(
6296    node: Node<'_>,
6297    ctx: &ScanCtx<'_>,
6298    target_owner: &CodeUnit,
6299) -> bool {
6300    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6301        return false;
6302    }
6303    let Some(name) = node
6304        .child_by_field_name("name")
6305        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6306    else {
6307        return false;
6308    };
6309    if node_text(name, ctx.source) != ctx.spec.member_name {
6310        return false;
6311    }
6312    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6313        return false;
6314    };
6315    let inherited = ctx.visibility.inherited_injected_class_owner(
6316        &ctx.analyzer,
6317        ctx.file,
6318        &enclosing_owner,
6319        &ctx.spec.member_name,
6320    );
6321    inherited.is_some_and(|owner| {
6322        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6323    })
6324}
6325
6326fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6327    if node.kind() == "function_definition" {
6328        maybe_record_free_function_definition_hit(node, ctx);
6329        return;
6330    }
6331    if node.kind() == "function_declarator" {
6332        maybe_record_recovered_error_free_function_call(node, ctx);
6333        return;
6334    }
6335    if node.kind() == "identifier" {
6336        maybe_record_free_function_value_reference(node, ctx);
6337        return;
6338    }
6339    if node.kind() != "call_expression" {
6340        return;
6341    }
6342    let Some(function) = node
6343        .child_by_field_name("function")
6344        .or_else(|| node.named_child(0))
6345    else {
6346        return;
6347    };
6348    let text = node_text(function, ctx.source);
6349    if !name_matches_callable(text, &ctx.spec.member_name) {
6350        return;
6351    }
6352    *ctx.raw_match_count += 1;
6353    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6354        match ctx
6355            .visibility
6356            .call_arity_evidence(ctx.file, node, ctx.source)
6357            .accepts(expected)
6358        {
6359            Some(true) => {}
6360            Some(false) => return,
6361            None => {
6362                // The argument count is unknown after macro expansion. It still
6363                // cannot select a *different* target when the bare name binds
6364                // to exactly one visible callable, so let the bare-call
6365                // resolution below prove that site; every other shape stays
6366                // unproven (#1811, the scan side of the same over-conservatism
6367                // that made the forward answer discard its lone candidate).
6368                if !bare_name_binds_only_target(node, function, text, ctx) {
6369                    push_unproven_hit(function_terminal_node(function), ctx);
6370                    return;
6371                }
6372            }
6373        }
6374    }
6375    if matches!(function.kind(), "identifier" | "template_function") {
6376        let terminal = function_terminal_node(function);
6377        let name = node_text(terminal, ctx.source);
6378        if ctx.local_shadows.is_shadowed(name) {
6379            return;
6380        }
6381        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6382            && !matches!(
6383                resolve_declaring_member_owner(
6384                    &ctx.analyzer,
6385                    ctx.visibility,
6386                    ctx.file,
6387                    &enclosing_owner,
6388                    name,
6389                ),
6390                EnclosingMemberOwnerResolution::Missing
6391            )
6392        {
6393            return;
6394        }
6395        match resolve_bare_call_target(
6396            node,
6397            function,
6398            &ctx.analyzer,
6399            ctx.visibility,
6400            &ctx.ordinary_type_imports,
6401            ctx.file,
6402            ctx.source,
6403        ) {
6404            BareCallTargetResolution::FreeFunctions(units)
6405                if units
6406                    .iter()
6407                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6408            {
6409                if free_function_call_may_target(node, text, ctx) {
6410                    let recursive = enclosing_context(terminal, ctx)
6411                        .enclosing
6412                        .as_ref()
6413                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6414                    if recursive {
6415                        push_recursive_reference_hit(terminal, ctx);
6416                    } else {
6417                        push_hit(terminal, ctx);
6418                    }
6419                }
6420            }
6421            BareCallTargetResolution::UnprovenFreeFunctions(units)
6422                if units
6423                    .iter()
6424                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6425            {
6426                push_unproven_hit(terminal, ctx);
6427            }
6428            BareCallTargetResolution::FreeFunctions(_)
6429            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6430            | BareCallTargetResolution::Type(_)
6431            | BareCallTargetResolution::CallableShadow => {}
6432            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6433                push_unproven_hit(terminal, ctx);
6434            }
6435        }
6436        return;
6437    }
6438    if !free_function_call_may_target(node, text, ctx) {
6439        return;
6440    }
6441    if ctx.visibility.contains_named_symbol(
6442        ctx.file,
6443        text,
6444        TargetKind::FreeFunction,
6445        &ctx.spec.target,
6446    ) {
6447        push_hit(function_terminal_node(function), ctx);
6448    } else if ctx.visibility.resolve_known_non_target(
6449        ctx.file,
6450        text,
6451        TargetKind::FreeFunction,
6452        &ctx.spec.target,
6453    ) {
6454        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6455        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6456    } else {
6457        push_unproven_hit(function_terminal_node(function), ctx);
6458    }
6459}
6460
6461fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6462    same_visible_symbol(unit, &ctx.spec.target)
6463        || ctx
6464            .visibility
6465            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6466            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6467                &ctx.analyzer,
6468                ctx.visibility,
6469                unit,
6470                &ctx.spec.target,
6471            )
6472}
6473
6474/// Recover a bare call whose adjacent object-like macro arguments made
6475/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6476///
6477/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6478/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6479/// real block-scope function declaration remains under a `declaration`, so the
6480/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6481/// out of declaration syntax. The malformed parameter list cannot establish
6482/// arity; publish a proven hit only when ordinary visibility leaves one
6483/// callable identity for the bare name.
6484fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6485    // The macro-declaration grammar keeps the same call as a bogus
6486    // `function_definition` whose damaged declarator ends at the block
6487    // literal's `(`: its parameter list is the call's argument prefix, so
6488    // unlike the ERROR shape it carries countable arity evidence.
6489    let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6490    if !block_call_envelope
6491        && (ctx
6492            .ancestry
6493            .parent(node)
6494            .is_none_or(|parent| parent.kind() != "ERROR")
6495            || !has_ancestor_kind(node, "compound_statement"))
6496    {
6497        return;
6498    }
6499    let mut recovered_functions = Vec::new();
6500    if let Some(function) = node
6501        .child_by_field_name("declarator")
6502        .filter(|function| function.kind() == "identifier")
6503    {
6504        recovered_functions.push(function);
6505    }
6506    if let Some(parameters) = node.child_by_field_name("parameters") {
6507        let mut cursor = parameters.walk();
6508        for parameter in parameters
6509            .named_children(&mut cursor)
6510            .filter(|child| child.kind() == "parameter_declaration")
6511        {
6512            if parameter
6513                .child_by_field_name("declarator")
6514                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6515                && let Some(function) = parameter
6516                    .child_by_field_name("type")
6517                    .filter(|function| function.kind() == "type_identifier")
6518            {
6519                recovered_functions.push(function);
6520            }
6521        }
6522    }
6523
6524    for function in recovered_functions {
6525        let name = node_text(function, ctx.source);
6526        if !name_matches_callable(name, &ctx.spec.member_name)
6527            || ctx.local_shadows.is_shadowed(name)
6528        {
6529            continue;
6530        }
6531        *ctx.raw_match_count += 1;
6532        if block_call_envelope
6533            && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6534            && ctx
6535                .visibility
6536                .call_arity_evidence(ctx.file, node, ctx.source)
6537                .accepts(expected)
6538                == Some(false)
6539        {
6540            continue;
6541        }
6542        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6543            push_hit(function, ctx);
6544            continue;
6545        }
6546        let mut candidates =
6547            ctx.visibility
6548                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6549        candidates.retain(|candidate| {
6550            ctx.visibility.declaration_visible_at(
6551                &ctx.analyzer,
6552                ctx.file,
6553                candidate,
6554                function.start_byte(),
6555            )
6556        });
6557        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6558        if candidates
6559            .iter()
6560            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6561        {
6562            push_unproven_hit(function, ctx);
6563        }
6564    }
6565}
6566
6567/// Whether the bare name at `call` binds to exactly one visible callable, and
6568/// that callable is the scan target.
6569///
6570/// This is the scan-side reading of the #1811 rule: with one name binding there
6571/// is nothing an unknown argument count could select instead, so the site is a
6572/// proven reference rather than an unproven one. Only bare identifiers qualify;
6573/// a member or qualified call reaches its target through a receiver this cannot
6574/// judge.
6575fn bare_name_binds_only_target(
6576    call: Node<'_>,
6577    function: Node<'_>,
6578    text: &str,
6579    ctx: &ScanCtx<'_>,
6580) -> bool {
6581    if !matches!(function.kind(), "identifier" | "template_function") {
6582        return false;
6583    }
6584    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6585}
6586
6587fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6588    let mut candidates = ctx
6589        .visibility
6590        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6591    candidates.retain(|candidate| {
6592        ctx.visibility
6593            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6594    });
6595    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6596    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6597}
6598
6599fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6600    if ctx.spec.param_types.is_none() {
6601        return true;
6602    }
6603    let mut candidates = ctx
6604        .visibility
6605        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6606    candidates.retain(|candidate| {
6607        ctx.visibility
6608            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6609    });
6610    callable_candidates_may_match_target(call, candidates, ctx)
6611}
6612
6613fn callable_candidates_may_match_target(
6614    call: Node<'_>,
6615    mut candidates: Vec<CodeUnit>,
6616    ctx: &ScanCtx<'_>,
6617) -> bool {
6618    let Some(arity) = ctx
6619        .visibility
6620        .call_arity_evidence(ctx.file, call, ctx.source)
6621        .exact()
6622    else {
6623        return true;
6624    };
6625    candidates.retain(|unit| {
6626        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6627    });
6628    if candidates.is_empty()
6629        || !candidates
6630            .iter()
6631            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6632    {
6633        return true;
6634    }
6635    let arg_types = call_argument_types(call, ctx);
6636    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6637        candidates,
6638        &arg_types,
6639        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6640        &|candidate| {
6641            ctx.visibility
6642                .callable_is_template_declaration(&ctx.analyzer, candidate)
6643        },
6644        &|name| ctx.visibility.resolve_type(ctx.file, name),
6645        &|left, right| same_visible_symbol(left, right),
6646    );
6647    filtered
6648        .iter()
6649        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6650}
6651
6652/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6653/// in order.
6654///
6655/// A `None` entry is an argument the index cannot type; one of them makes
6656/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6657fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6658    argument_children(arguments)
6659        .map(|arg| expression_arg_type(arg, ctx))
6660        .collect()
6661}
6662
6663fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6664    call_arguments_node(call)
6665        .map(|arguments| argument_list_types(arguments, ctx))
6666        .unwrap_or_default()
6667}
6668
6669fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6670    match node.kind() {
6671        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6672        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6673            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6674            literal
6675        }),
6676        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6677        // `T v(x);` is the vexing parse: the grammar reads it as a function
6678        // declaration whose one parameter is `x` spelled as a type. A parameter
6679        // declaration that is nothing but a type identifier is that reading, and
6680        // the identifier it holds is the constructor argument.
6681        "parameter_declaration" => {
6682            let declared = node.child_by_field_name("type")?;
6683            (node.child_by_field_name("declarator").is_none()
6684                && declared.kind() == "type_identifier")
6685                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6686                .flatten()
6687        }
6688        // `this->m_params` names the same data member as the bare `m_params`.
6689        "field_expression" => {
6690            let receiver = node
6691                .child_by_field_name("argument")
6692                .or_else(|| node.named_child(0))?;
6693            let field = node.child_by_field_name("field")?;
6694            (receiver.kind() == "this")
6695                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6696                .flatten()
6697        }
6698        "parenthesized_expression" => node
6699            .child_by_field_name("argument")
6700            .or_else(|| node.named_child(0))
6701            .and_then(|inner| expression_arg_type(inner, ctx)),
6702        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
6703        // forward (#2552).
6704        "call_expression" => {
6705            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6706            expression_arg_type(forwarded, ctx)
6707        }
6708        "pointer_expression" => {
6709            let delta = match node.child_by_field_name("operator")?.kind() {
6710                "&" => 1,
6711                "*" => -1,
6712                _ => return None,
6713            };
6714            let inner = node
6715                .child_by_field_name("argument")
6716                .or_else(|| node.named_child(0))?;
6717            let mut arg_type = expression_arg_type(inner, ctx)?;
6718            arg_type.indirection += delta;
6719            Some(arg_type)
6720        }
6721        _ => None,
6722    }
6723}
6724
6725/// The type of a bare value name used as a call argument: the local or
6726/// parameter binding when one is in scope, otherwise the enclosing class's data
6727/// member of that name.
6728///
6729/// Unqualified lookup reaches a local before a data member, so a local of the
6730/// same name ends the search rather than falling through to the field. This
6731/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
6732/// the two directions agreeing about which overload a member-field argument
6733/// selects (#2894).
6734fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6735    match ctx.bindings.resolve_symbol(name) {
6736        SymbolResolution::Precise(bindings) => {
6737            bindings.iter().find_map(CppScanBinding::as_arg_type)
6738        }
6739        SymbolResolution::Ambiguous => None,
6740        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
6741            .then(|| enclosing_member_field_arg_type(node, name, ctx))
6742            .flatten(),
6743    }
6744}
6745
6746/// The declared type of `name` read as a data member of the class that lexically
6747/// owns `node`.
6748///
6749/// `None` when there is no enclosing class, when the name is not one data member
6750/// of it, or when the member's declared type does not resolve. An unknown
6751/// argument type makes the overload filter keep every candidate, which is the
6752/// honest answer for a field the index cannot type.
6753fn enclosing_member_field_arg_type(
6754    node: Node<'_>,
6755    name: &str,
6756    ctx: &ScanCtx<'_>,
6757) -> Option<CppArgType> {
6758    let owner = enclosing_context(node, ctx).owner?;
6759    let fields = ctx
6760        .visibility
6761        .visible_members_for_owner_name(ctx.file, &owner, name)
6762        .into_iter()
6763        .filter(|unit| unit.is_field())
6764        .collect::<Vec<_>>();
6765    let [field] = fields.as_slice() else {
6766        return None;
6767    };
6768    let (type_name, unit, indirection) =
6769        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
6770    Some(CppArgType {
6771        name: type_name,
6772        unit,
6773        indirection,
6774        pointee_const: false,
6775    })
6776}
6777
6778/// Record a *non-call* reference to a free function used as a value: `&foo`,
6779/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
6780/// `foo()` is recorded by the call_expression arm, and the function's own
6781/// declaration/definition name is not a reference.
6782fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6783    let text = node_text(node, ctx.source);
6784    if !name_matches_callable(text, &ctx.spec.member_name) {
6785        return;
6786    }
6787    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
6788        *ctx.raw_match_count += 1;
6789        match kind {
6790            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
6791            RecoveredCallableDeclarationKind::Definition => {
6792                push_recovered_definition_hit(node, ctx)
6793            }
6794        }
6795        return;
6796    }
6797    if is_declaration_name(node) {
6798        maybe_record_free_function_declaration_reference(node, ctx);
6799        return;
6800    }
6801    if is_call_callee_node(node) {
6802        return;
6803    }
6804    *ctx.raw_match_count += 1;
6805    if ctx.visibility.contains_named_symbol(
6806        ctx.file,
6807        text,
6808        TargetKind::FreeFunction,
6809        &ctx.spec.target,
6810    ) {
6811        push_hit(node, ctx);
6812    } else if ctx.visibility.resolve_known_non_target(
6813        ctx.file,
6814        text,
6815        TargetKind::FreeFunction,
6816        &ctx.spec.target,
6817    ) {
6818        // A qualified reference proven to a different namespace is not a match.
6819    } else {
6820        push_unproven_hit(node, ctx);
6821    }
6822}
6823
6824#[derive(Clone, Copy)]
6825enum RecoveredCallableDeclarationKind {
6826    Declaration,
6827    Definition,
6828}
6829
6830/// Recover the role of a callable name whose declaration was split by an
6831/// ERROR node. An indexed declaration range proves identity when available;
6832/// for a top-level C callable that recovery failed to range-index, same-file
6833/// name identity is sufficient because C has no function overloading. The CST
6834/// shape distinguishes a prototype from a definition. C++ parsing of
6835/// an unknown C return type can put the name under an ERROR child of the
6836/// function declarator, while call-shaped recovery puts that declarator under
6837/// an ERROR whose parent is a compound statement; only the former is admitted.
6838fn recovered_callable_declaration_kind(
6839    node: Node<'_>,
6840    ctx: &ScanCtx<'_>,
6841) -> Option<RecoveredCallableDeclarationKind> {
6842    if !matches!(node.kind(), "identifier" | "field_identifier") {
6843        return None;
6844    }
6845    let target_identity_proven =
6846        ctx.target_declaration_ranges.iter().any(|range| {
6847            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6848        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
6849            && ctx.spec.target.is_function()
6850            && ctx.spec.target.source() == ctx.file);
6851    let mut current = ctx.ancestry.parent(node);
6852    while let Some(parent) = current {
6853        if parent.is_error() {
6854            // C files with an unknown return type can retain the real
6855            // callable name as `function_declarator > ERROR`, with the ERROR
6856            // as a named child rather than the declarator field itself.
6857            let error = parent;
6858            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
6859                function.kind() == "function_declarator"
6860                    && function
6861                        .child_by_field_name("parameters")
6862                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
6863            }) else {
6864                current = ctx.ancestry.parent(parent);
6865                continue;
6866            };
6867            let container = ctx.ancestry.parent(function)?;
6868            let kind = match container.kind() {
6869                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6870                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6871                _ => return None,
6872            };
6873            return target_identity_proven.then_some(kind);
6874        }
6875        if parent.kind() == "function_declarator" {
6876            let error = ctx
6877                .ancestry
6878                .parent(parent)
6879                .filter(|parent| parent.is_error())?;
6880            let declarator = parent.child_by_field_name("declarator")?;
6881            if declarator.start_byte() > node.start_byte()
6882                || node.end_byte() > declarator.end_byte()
6883            {
6884                return None;
6885            }
6886            let container = ctx.ancestry.parent(error)?;
6887            let kind = match container.kind() {
6888                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6889                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6890                _ => return None,
6891            };
6892            return target_identity_proven.then_some(kind);
6893        }
6894        if parent.is_error() {
6895            current = ctx.ancestry.parent(parent);
6896            continue;
6897        }
6898        if matches!(
6899            parent.kind(),
6900            "translation_unit" | "function_definition" | "compound_statement"
6901        ) {
6902            return None;
6903        }
6904        current = ctx.ancestry.parent(parent);
6905    }
6906    None
6907}
6908
6909fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6910    if !ctx.spec.callable_has_definition_body {
6911        return;
6912    }
6913    let text = node_text(node, ctx.source);
6914    if !name_matches_callable(text, &ctx.spec.member_name) {
6915        return;
6916    }
6917    let mut declaration = ctx.ancestry.parent(node);
6918    while let Some(candidate) = declaration {
6919        if candidate.kind() == "function_definition" {
6920            return;
6921        }
6922        if candidate.kind() == "declaration" {
6923            break;
6924        }
6925        declaration = ctx.ancestry.parent(candidate);
6926    }
6927    if declaration.is_none() {
6928        return;
6929    }
6930    let linked_declaration = ctx
6931        .visibility
6932        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
6933        .into_iter()
6934        .find(|candidate| {
6935            candidate.source() == ctx.file
6936                && candidate.is_function()
6937                && (!ctx.target_group.contains(candidate)
6938                    || candidate.source() == ctx.spec.target.source())
6939                && ctx.analyzer.ranges(candidate).iter().any(|range| {
6940                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6941                })
6942                && (candidate.source() == ctx.spec.target.source()
6943                    && candidate.fq_name() == ctx.spec.target.fq_name()
6944                    && candidate.signature() == ctx.spec.target.signature()
6945                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
6946                        &ctx.analyzer,
6947                        ctx.visibility,
6948                        candidate,
6949                        &ctx.spec.target,
6950                    ))
6951                && callable_arity_matches_target(candidate, node, ctx)
6952        });
6953    if linked_declaration.is_none() {
6954        return;
6955    }
6956    *ctx.raw_match_count += 1;
6957    push_declaration_reference_hit(node, ctx);
6958}
6959
6960fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6961    let Some(function) = function_definition_name_node(node) else {
6962        return;
6963    };
6964    let text = node_text(function, ctx.source);
6965    if !name_matches_callable(text, &ctx.spec.member_name) {
6966        return;
6967    }
6968    *ctx.raw_match_count += 1;
6969    if !function_definition_signature_matches_target(node, ctx) {
6970        return;
6971    }
6972    match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
6973        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
6974        DefinitionNameTargetResolution::KnownNonTarget => {
6975            // A definition in another explicit namespace is a proven non-match.
6976        }
6977        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
6978    }
6979}
6980
6981fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6982    if node.kind() == "preproc_arg" {
6983        maybe_record_function_macro_replacement_method_hits(node, ctx);
6984        return;
6985    }
6986    if node.kind() == "using_declaration" {
6987        maybe_record_using_callable_hit(node, ctx);
6988        return;
6989    }
6990    if has_ancestor_kind(node, "using_declaration") {
6991        return;
6992    }
6993    if node.kind() == "function_definition" {
6994        maybe_record_method_definition_hit(node, ctx);
6995        return;
6996    }
6997    if is_declaration_name(node) {
6998        return;
6999    }
7000    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7001        maybe_record_qualified_method_value_hit(node, member, ctx);
7002        return;
7003    }
7004    if let Some(value) = qualified_callable_value(node) {
7005        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7006        return;
7007    }
7008    if let Some(call) = recovered_relational_template_member_call(node) {
7009        maybe_record_recovered_relational_template_method_hit(call, ctx);
7010        return;
7011    }
7012    if node.kind() != "call_expression" {
7013        return;
7014    }
7015    if let Some((receiver, operator)) = explicit_operator_call(node) {
7016        let text = node_text(operator, ctx.source);
7017        if !name_matches_callable(text, &ctx.spec.member_name) {
7018            return;
7019        }
7020        *ctx.raw_match_count += 1;
7021        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7022            match ctx
7023                .visibility
7024                .call_arity_evidence(ctx.file, node, ctx.source)
7025                .accepts(expected)
7026            {
7027                Some(true) => {}
7028                Some(false) => return,
7029                None => {
7030                    push_unproven_hit(operator, ctx);
7031                    return;
7032                }
7033            }
7034        }
7035        let arity = ctx
7036            .visibility
7037            .call_arity_evidence(ctx.file, node, ctx.source)
7038            .exact();
7039        record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7040        return;
7041    }
7042    let Some(function) = node
7043        .child_by_field_name("function")
7044        .or_else(|| node.named_child(0))
7045    else {
7046        return;
7047    };
7048    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7049        return;
7050    }
7051    if function.kind() == "identifier"
7052        && ctx
7053            .local_shadows
7054            .is_shadowed(node_text(function, ctx.source))
7055    {
7056        return;
7057    }
7058    *ctx.raw_match_count += 1;
7059    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7060        match ctx
7061            .visibility
7062            .call_arity_evidence(ctx.file, node, ctx.source)
7063            .accepts(expected)
7064        {
7065            Some(true) => {}
7066            Some(false) => return,
7067            None => {
7068                push_unproven_hit(function_terminal_node(function), ctx);
7069                return;
7070            }
7071        }
7072    }
7073    if !method_call_may_target(node, ctx) {
7074        return;
7075    }
7076    if is_structurally_qualified(function) {
7077        match qualified_owner_resolution(function, ctx) {
7078            QualifiedOwnerResolution::Target => {
7079                push_hit(function_terminal_node(function), ctx);
7080            }
7081            QualifiedOwnerResolution::NonTarget => {}
7082            QualifiedOwnerResolution::Unresolved => {
7083                push_unproven_hit(function_terminal_node(function), ctx);
7084            }
7085        }
7086        return;
7087    }
7088    match call_function_target_resolution(function, ctx) {
7089        MethodReceiverTargetResolution::Target
7090            if call_function_has_direct_self_receiver(
7091                function,
7092                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7093            ) =>
7094        {
7095            push_self_receiver_hit(function_terminal_node(function), ctx);
7096        }
7097        MethodReceiverTargetResolution::Target => {
7098            push_hit(function_terminal_node(function), ctx);
7099        }
7100        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7101        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7102        // the target member declared on a base is a genuine external usage of that inherited
7103        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7104        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7105        // arm because `same_owner_context` also accepts this inherited case.
7106        MethodReceiverTargetResolution::Missing
7107            if inherited_target_owner_context(function, ctx) =>
7108        {
7109            push_hit(function_terminal_node(function), ctx);
7110        }
7111        MethodReceiverTargetResolution::Missing
7112            if (matches!(function.kind(), "identifier" | "template_function")
7113                || call_function_has_direct_self_receiver(
7114                    function,
7115                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7116                ))
7117                && (same_owner_context(function, ctx)
7118                    || out_of_line_target_owner_context(function, ctx)) =>
7119        {
7120            push_self_receiver_hit(function_terminal_node(function), ctx);
7121        }
7122        MethodReceiverTargetResolution::Missing
7123            if function.kind() == "identifier"
7124                && resolves_to_lexical_free_function(function, ctx) =>
7125        {
7126            // A visible namespace/free function is a proven negative once the
7127            // enclosing structured owner and its hierarchy contain no such member.
7128        }
7129        MethodReceiverTargetResolution::Missing
7130            if !receiver_has_known_non_target(function, ctx)
7131                && !known_non_target_owner_context(function, ctx) =>
7132        {
7133            push_unproven_hit(function_terminal_node(function), ctx);
7134        }
7135        MethodReceiverTargetResolution::Missing => {}
7136    }
7137}
7138
7139/// Report member calls written inside a function-like macro's replacement.
7140///
7141/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7142/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7143/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7144/// structure, and every hit is reported at the bytes the member spells inside
7145/// that token.
7146fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7147    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7148        parent.kind() == "preproc_function_def"
7149            && parent
7150                .child_by_field_name("value")
7151                .is_some_and(|value| same_node(value, node))
7152    }) else {
7153        return;
7154    };
7155    let Some(body) = ctx
7156        .visibility
7157        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7158    else {
7159        return;
7160    };
7161    let Some(statements) = body.statements() else {
7162        return;
7163    };
7164    // Collect the candidate calls before typing anything: a replacement that
7165    // never names the queried member must not pay for its declarations.
7166    let mut candidates = Vec::new();
7167    let mut stack = vec![statements];
7168    while let Some(current) = stack.pop() {
7169        push_named_children_reversed(current, &mut stack);
7170        if current.kind() != "call_expression" {
7171            continue;
7172        }
7173        let Some(function) = current
7174            .child_by_field_name("function")
7175            .filter(|function| function.kind() == "field_expression")
7176        else {
7177            continue;
7178        };
7179        let Some(member) = function.child_by_field_name("field") else {
7180            continue;
7181        };
7182        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7183            continue;
7184        }
7185        let Some(receiver) = function
7186            .child_by_field_name("argument")
7187            .or_else(|| function.child_by_field_name("object"))
7188            .or_else(|| function.named_child(0))
7189        else {
7190            continue;
7191        };
7192        candidates.push((current, member, receiver));
7193    }
7194    if candidates.is_empty() {
7195        return;
7196    }
7197    let Some(replacement_start) =
7198        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7199    else {
7200        return;
7201    };
7202    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7203    for (current, member, receiver) in candidates {
7204        if *ctx.limit_exceeded {
7205            return;
7206        }
7207        let range = body.file_range(member, replacement_start);
7208        debug_assert_eq!(
7209            ctx.source.get(range.clone()),
7210            Some(node_text(member, &body.source)),
7211            "macro replacement member range must spell the member name"
7212        );
7213        *ctx.raw_match_count += 1;
7214        // The replacement's own macro environment is the one at the definition,
7215        // not at the sentinel offsets the recovered call carries.
7216        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7217            ctx.file,
7218            current,
7219            &body.source,
7220            definition.start_byte(),
7221        );
7222        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7223            match arity_evidence.accepts(expected) {
7224                Some(true) => {}
7225                Some(false) => continue,
7226                None => {
7227                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7228                    continue;
7229                }
7230            }
7231        }
7232        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7233        {
7234            MacroReplacementReceiver::Parameter => {
7235                if target_member_is_visible_candidate(ctx) {
7236                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7237                }
7238                continue;
7239            }
7240            MacroReplacementReceiver::Unknown => continue,
7241            MacroReplacementReceiver::Units(units) => {
7242                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7243            }
7244        };
7245        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7246            MethodReceiverTargetResolution::Target => {
7247                push_reference_hit_range(node, range.start, range.end, ctx);
7248            }
7249            MethodReceiverTargetResolution::Missing => {
7250                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7251            }
7252            MethodReceiverTargetResolution::NonTarget
7253            | MethodReceiverTargetResolution::Ambiguous => {}
7254        }
7255    }
7256}
7257
7258enum MacroReplacementReceiver {
7259    /// The receiver is one of the macro's parameters. Its type is supplied by
7260    /// each invocation, so a member call on it can never be proven here.
7261    Parameter,
7262    /// The receiver's declared type resolved to these units.
7263    Units(Vec<CodeUnit>),
7264    /// The receiver has a shape this path does not type.
7265    Unknown,
7266}
7267
7268fn macro_replacement_receiver(
7269    receiver: Node<'_>,
7270    body: &ParsedReplacementBody,
7271    locals: &HashMap<String, Option<CodeUnit>>,
7272    anchor: Node<'_>,
7273    ctx: &ScanCtx<'_>,
7274) -> MacroReplacementReceiver {
7275    let mut current = receiver;
7276    while matches!(
7277        current.kind(),
7278        "parenthesized_expression" | "pointer_expression"
7279    ) {
7280        let Some(inner) = current
7281            .child_by_field_name("argument")
7282            .or_else(|| current.named_child(0))
7283        else {
7284            return MacroReplacementReceiver::Unknown;
7285        };
7286        current = inner;
7287    }
7288    if !matches!(current.kind(), "identifier" | "field_identifier") {
7289        return MacroReplacementReceiver::Unknown;
7290    }
7291    let name = node_text(current, &body.source);
7292    if body.parameters.iter().any(|parameter| parameter == name) {
7293        return MacroReplacementReceiver::Parameter;
7294    }
7295    if let Some(local) = locals.get(name) {
7296        return match local {
7297            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7298            None => MacroReplacementReceiver::Unknown,
7299        };
7300    }
7301    // Neither a parameter nor a replacement-local: an ordinary name the macro
7302    // definition site sees, resolved exactly as the ordinary receiver path
7303    // resolves a bare identifier.
7304    let global_fields = ctx
7305        .visibility
7306        .visible_identifier_candidates(ctx.file, name)
7307        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7308        .collect::<Vec<_>>();
7309    if !global_fields.is_empty() {
7310        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7311            global_fields,
7312            anchor,
7313            ctx,
7314        ));
7315    }
7316    MacroReplacementReceiver::Units(
7317        ctx.visibility
7318            .resolve_type(ctx.file, name)
7319            .into_iter()
7320            .collect(),
7321    )
7322}
7323
7324/// Type the locals a macro replacement declares, keyed by declared name.
7325///
7326/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7327/// serves: the receiver's type is stated inside the replacement itself. A name
7328/// whose declared type does not resolve is kept with no unit, so a call on it
7329/// stays silent instead of falling through to file-scope name lookup.
7330fn macro_replacement_local_receivers(
7331    statements: Node<'_>,
7332    body: &ParsedReplacementBody,
7333    ctx: &ScanCtx<'_>,
7334) -> HashMap<String, Option<CodeUnit>> {
7335    let mut locals = HashMap::default();
7336    let mut stack = vec![statements];
7337    while let Some(current) = stack.pop() {
7338        push_named_children_reversed(current, &mut stack);
7339        if current.kind() != "declaration" {
7340            continue;
7341        }
7342        let Some(type_node) = current
7343            .child_by_field_name("type")
7344            .or_else(|| first_type_child(current))
7345        else {
7346            continue;
7347        };
7348        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7349        let mut cursor = current.walk();
7350        for child in current.named_children(&mut cursor) {
7351            let declarator = if child.kind() == "init_declarator" {
7352                child.child_by_field_name("declarator")
7353            } else {
7354                is_declarator_node(child).then_some(child)
7355            };
7356            let Some(name) =
7357                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7358            else {
7359                continue;
7360            };
7361            locals.insert(name, unit.clone());
7362        }
7363    }
7364    locals
7365}
7366
7367fn macro_replacement_declared_unit(
7368    type_node: Node<'_>,
7369    body: &ParsedReplacementBody,
7370    ctx: &ScanCtx<'_>,
7371) -> Option<CodeUnit> {
7372    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7373    let unit = match ctx
7374        .visibility
7375        .resolve_type_node_result(ctx.file, type_node, &body.source)
7376    {
7377        Ok(Some(unit)) => Some(unit),
7378        Ok(None) => ctx
7379            .visibility
7380            .canonical_type_for_reference(ctx.file, &name)
7381            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7382        Err(_) => None,
7383    }?;
7384    canonical_receiver_unit(&unit, ctx)
7385}
7386
7387/// Whether the queried member is a candidate this file can see.
7388///
7389/// A macro parameter receiver has no type at the definition, so a member call
7390/// on it is at most unproven. Requiring the target to be visible here keeps a
7391/// same-named member of an unrelated translation unit out of the result.
7392fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7393    ctx.visibility
7394        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7395        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7396}
7397
7398fn maybe_record_recovered_relational_template_method_hit(
7399    call: RecoveredRelationalTemplateMemberCall<'_>,
7400    ctx: &mut ScanCtx<'_>,
7401) {
7402    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7403        return;
7404    }
7405    *ctx.raw_match_count += 1;
7406    if !ctx
7407        .visibility
7408        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7409        || ctx
7410            .spec
7411            .callable_arity_at(call.member.start_byte())
7412            .is_some_and(|arity| !arity.accepts(call.arity))
7413    {
7414        return;
7415    }
7416    record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7417}
7418
7419fn record_explicit_receiver_method_hit(
7420    receiver: Node<'_>,
7421    member: Node<'_>,
7422    arity: Option<usize>,
7423    ctx: &mut ScanCtx<'_>,
7424) {
7425    match explicit_receiver_target_resolution(receiver, arity, ctx) {
7426        MethodReceiverTargetResolution::Target
7427            if receiver_is_self_like(
7428                receiver,
7429                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7430            ) =>
7431        {
7432            push_self_receiver_hit(member, ctx);
7433        }
7434        MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7435        MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7436        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7437    }
7438}
7439
7440fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7441    if node.kind() != "qualified_identifier" {
7442        return None;
7443    }
7444    let parameter = node
7445        .parent()
7446        .filter(|parent| parent.kind() == "parameter_declaration")?;
7447    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7448    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7449    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7450    // and `(arg)` as an abstract function declarator. Ordinary qualified
7451    // parameter types have named/pointer/reference declarators instead.
7452    if parameter.child_by_field_name("type") != Some(node)
7453        || parameter_declarator.kind() != "abstract_function_declarator"
7454    {
7455        return None;
7456    }
7457    let parameter_list = parameter
7458        .parent()
7459        .filter(|parent| parent.kind() == "parameter_list")?;
7460    if parameter_list.named_child_count() != 1 {
7461        return None;
7462    }
7463    let function_declarator = parameter_list
7464        .parent()
7465        .filter(|parent| parent.kind() == "function_declarator")?;
7466    if function_declarator
7467        .child_by_field_name("declarator")
7468        .is_none_or(|declarator| declarator.kind() != "identifier")
7469        || function_declarator
7470            .parent()
7471            .is_none_or(|parent| parent.kind() != "declaration")
7472    {
7473        return None;
7474    }
7475    node.child_by_field_name("name")
7476}
7477
7478fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7479    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7480        return;
7481    };
7482    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7483        return;
7484    }
7485    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7486        return;
7487    };
7488    *ctx.raw_match_count += 1;
7489    let owner_resolution = qualified_owner_components(imported, ctx.source)
7490        .map(|qualified| {
7491            let lexical_scope = match enclosing_lexical_scope_components(
7492                imported,
7493                &ctx.analyzer,
7494                ctx.visibility,
7495                ctx.file,
7496                ctx.source,
7497            ) {
7498                LexicalScopeResolution::Resolved(scope) => scope,
7499                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7500                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7501            };
7502            ctx.visibility.resolve_type_components_lexically(
7503                &ctx.analyzer,
7504                ctx.file,
7505                &qualified.names,
7506                qualified.global,
7507                &lexical_scope,
7508            )
7509        })
7510        .unwrap_or(LexicalTypeResolution::Missing);
7511    let matches_target_owner = matches!(
7512        owner_resolution,
7513        LexicalTypeResolution::Resolved {
7514            ref unit,
7515            ref candidates,
7516            ..
7517        } if same_visible_symbol(unit, target_owner)
7518            || candidates
7519                .iter()
7520                .any(|candidate| same_visible_symbol(candidate, target_owner))
7521    );
7522    if !matches_target_owner {
7523        match owner_resolution {
7524            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7525                push_unproven_hit(imported, ctx);
7526            }
7527            LexicalTypeResolution::Resolved { .. } => {}
7528        }
7529        return;
7530    }
7531    match visible_target_callable_member_resolution(target_owner, ctx) {
7532        VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7533        VisibleTargetCallableMemberResolution::NonCallable => {}
7534        VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7535    }
7536}
7537
7538enum VisibleTargetCallableMemberResolution {
7539    Target,
7540    NonCallable,
7541    Unresolved,
7542}
7543
7544fn visible_target_callable_member_resolution(
7545    owner: &CodeUnit,
7546    ctx: &ScanCtx<'_>,
7547) -> VisibleTargetCallableMemberResolution {
7548    match ctx
7549        .visibility
7550        .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7551    {
7552        VisibleMemberResolution::Callable(candidates)
7553            if candidates.iter().all(|candidate| {
7554                ctx.target_group.contains(candidate)
7555                    || ctx
7556                        .target_group
7557                        .iter()
7558                        .any(|target| same_visible_symbol(candidate, target))
7559            }) =>
7560        {
7561            VisibleTargetCallableMemberResolution::Target
7562        }
7563        VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7564        VisibleMemberResolution::Callable(_)
7565        | VisibleMemberResolution::AmbiguousKind
7566        | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7567    }
7568}
7569
7570fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7571    let name = node_text(node, ctx.source);
7572    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7573    let key = (namespace.clone(), name.to_string());
7574    if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7575        return resolved;
7576    }
7577    let resolved = ctx
7578        .visibility
7579        .visible_identifier_candidates(ctx.file, name)
7580        .any(|unit| {
7581            unit.is_function()
7582                && type_owner_of(&ctx.analyzer, unit).is_none()
7583                && unit.package_name() == namespace
7584        });
7585    ctx.lexical_free_function_cache
7586        .borrow_mut()
7587        .insert(key, resolved);
7588    resolved
7589}
7590
7591fn maybe_record_qualified_method_value_hit(
7592    qualified: Node<'_>,
7593    member: Node<'_>,
7594    ctx: &mut ScanCtx<'_>,
7595) {
7596    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7597        return;
7598    }
7599    *ctx.raw_match_count += 1;
7600    let resolution =
7601        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7602    match resolution {
7603        LexicalCallableValueResolution::Type(resolved_owner) => {
7604            let Some(owner) = ctx.spec.owner.as_ref() else {
7605                push_unproven_hit(member, ctx);
7606                return;
7607            };
7608            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7609                if same_visible_symbol(&resolved_owner, owner) {
7610                    push_unproven_hit(member, ctx);
7611                }
7612                return;
7613            }
7614            // An explicitly qualified method value remains an external
7615            // reference even when its owner is the enclosing class.
7616            match visible_target_callable_member_resolution(owner, ctx) {
7617                VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7618                VisibleTargetCallableMemberResolution::NonCallable => {}
7619                VisibleTargetCallableMemberResolution::Unresolved => {
7620                    push_unproven_hit(member, ctx);
7621                }
7622            }
7623        }
7624        LexicalCallableValueResolution::FreeFunction(_) => {}
7625        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7626            push_unproven_hit(member, ctx);
7627        }
7628    }
7629}
7630
7631fn qualified_callable_value_resolution(
7632    qualified: Node<'_>,
7633    member_name: &str,
7634    ctx: &ScanCtx<'_>,
7635) -> LexicalCallableValueResolution {
7636    let Some((owner_components, global)) =
7637        qualified_callable_owner_components(qualified, ctx.source)
7638    else {
7639        return LexicalCallableValueResolution::Missing;
7640    };
7641    let lexical_scope = if global {
7642        Vec::new()
7643    } else {
7644        match enclosing_lexical_scope_components(
7645            qualified,
7646            &ctx.analyzer,
7647            ctx.visibility,
7648            ctx.file,
7649            ctx.source,
7650        ) {
7651            LexicalScopeResolution::Resolved(scope) => scope,
7652            LexicalScopeResolution::Ambiguous => {
7653                return LexicalCallableValueResolution::Ambiguous;
7654            }
7655            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7656        }
7657    };
7658    if let Some(target_owner) = ctx.spec.owner.as_ref()
7659        && let LexicalTypeResolution::Resolved { unit, .. } =
7660            resolve_type_components_lexically_at_for_target_with_scope_cache(
7661                qualified,
7662                &owner_components,
7663                global,
7664                &ctx.analyzer,
7665                ctx.visibility,
7666                &ctx.ordinary_type_imports,
7667                ctx.file,
7668                ctx.source,
7669                target_owner,
7670                false,
7671                Some(&ctx.lexical_scope_cache),
7672            )
7673    {
7674        return LexicalCallableValueResolution::Type(unit);
7675    }
7676    ctx.visibility.resolve_callable_value_components_lexically(
7677        &ctx.analyzer,
7678        ctx.file,
7679        &owner_components,
7680        member_name,
7681        global,
7682        &lexical_scope,
7683    )
7684}
7685
7686fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7687    let Some(owner) = ctx.spec.owner.as_ref() else {
7688        return true;
7689    };
7690    if ctx.spec.param_types.is_none() {
7691        return true;
7692    }
7693    let candidates = ctx
7694        .visibility
7695        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7696        .into_iter()
7697        .filter(|unit| unit.is_function())
7698        .cloned()
7699        .collect::<Vec<_>>();
7700    callable_candidates_may_match_target(call, candidates, ctx)
7701}
7702
7703fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7704    let Some(function) = function_definition_name_node(node) else {
7705        return;
7706    };
7707    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7708        return;
7709    }
7710    *ctx.raw_match_count += 1;
7711    if !function_definition_signature_matches_target(node, ctx) {
7712        return;
7713    }
7714    if node_inside_target_declaration(function, ctx) {
7715        return;
7716    }
7717    if is_structurally_qualified(function) {
7718        match qualified_owner_resolution(function, ctx) {
7719            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
7720            QualifiedOwnerResolution::NonTarget => {}
7721            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7722        }
7723        return;
7724    }
7725    match definition_name_target_resolution(function, TargetKind::Method, ctx) {
7726        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7727        DefinitionNameTargetResolution::KnownNonTarget => {
7728            // A method definition for another visible owner is a proven non-match.
7729        }
7730        DefinitionNameTargetResolution::Unresolved
7731            if known_non_target_owner_context(function, ctx) => {}
7732        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7733    }
7734}
7735
7736pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7737    let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
7738    ctx.target_declaration_ranges
7739        .iter()
7740        .any(|range| range.contains(&node_range))
7741}
7742
7743fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
7744    let mut receiver = None;
7745    let mut cursor = node.walk();
7746    for child in node.named_children(&mut cursor) {
7747        if child.kind() == "argument_list" {
7748            continue;
7749        }
7750        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
7751            return receiver.map(|receiver| (receiver, operator));
7752        }
7753        if receiver.is_none() {
7754            receiver = Some(child);
7755        }
7756    }
7757    None
7758}
7759
7760/// Return the name node of a C++ function definition's declarator.
7761pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
7762    if node.kind() != "function_definition" {
7763        return None;
7764    }
7765    node.child_by_field_name("declarator")
7766        .and_then(declarator_name_node)
7767}
7768
7769fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
7770    function_definition_name_node(node)
7771}
7772
7773fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7774    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7775        return true;
7776    };
7777    let Some(candidate) = canonical_callable_at_node(
7778        node,
7779        &ctx.analyzer,
7780        ctx.visibility,
7781        ctx.file,
7782        ctx.source,
7783        ctx.spec.kind,
7784    ) else {
7785        return false;
7786    };
7787    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
7788        return false;
7789    };
7790    if !callable_arity_matches(expected, actual_arity) {
7791        return false;
7792    }
7793    ctx.spec.param_types.as_ref().is_none_or(|expected| {
7794        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
7795            .is_some_and(|actual| actual == *expected)
7796    })
7797}
7798
7799fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7800    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7801        return true;
7802    };
7803    cpp_callable_arity(&ctx.analyzer, candidate)
7804        .is_some_and(|actual| callable_arity_matches(expected, actual))
7805}
7806
7807fn callable_arity_matches(
7808    expected: brokk_bifrost_core::analyzer::model::CallableArity,
7809    actual: brokk_bifrost_core::analyzer::model::CallableArity,
7810) -> bool {
7811    expected.accepts(actual.total())
7812}
7813
7814fn canonical_callable_at_node(
7815    node: Node<'_>,
7816    analyzer: &CppGraphSource<'_>,
7817    visibility: &VisibilityIndex<'_>,
7818    file: &ProjectFile,
7819    source: &str,
7820    kind: TargetKind,
7821) -> Option<CodeUnit> {
7822    let name = callable_name_node(node)?;
7823    let name_range = (name.start_byte(), name.end_byte());
7824    let name_text = node_text(name, source);
7825    for candidate in visibility.named_candidates(file, name_text, kind) {
7826        if candidate.source() != file || !candidate.is_function() {
7827            continue;
7828        }
7829        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
7830            continue;
7831        };
7832        for occurrence in occurrences {
7833            if occurrence
7834                .name_range
7835                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
7836            {
7837                return Some(candidate);
7838            }
7839        }
7840    }
7841    None
7842}
7843
7844fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
7845    let name = if node.kind() == "function_definition" {
7846        function_definition_name_node(node)?
7847    } else {
7848        node.child_by_field_name("declarator")
7849            .and_then(declarator_name_node)?
7850    };
7851    Some(function_terminal_node(name))
7852}
7853
7854fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
7855    name_matches_callable(node_text(function_terminal_node(node), source), expected)
7856}
7857
7858enum DefinitionNameTargetResolution {
7859    Target,
7860    KnownNonTarget,
7861    Unresolved,
7862}
7863
7864fn definition_name_target_resolution(
7865    function: Node<'_>,
7866    target_kind: TargetKind,
7867    ctx: &ScanCtx<'_>,
7868) -> DefinitionNameTargetResolution {
7869    let candidates = definition_name_candidates(function, ctx);
7870    if candidates
7871        .iter()
7872        .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
7873        .any(|name| {
7874            ctx.visibility
7875                .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
7876        })
7877    {
7878        DefinitionNameTargetResolution::Target
7879    } else if candidates.iter().any(|name| {
7880        ctx.visibility
7881            .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
7882    }) {
7883        DefinitionNameTargetResolution::KnownNonTarget
7884    } else {
7885        DefinitionNameTargetResolution::Unresolved
7886    }
7887}
7888
7889fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
7890    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
7891    if raw.is_empty() {
7892        return Vec::new();
7893    }
7894    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
7895        return vec![raw];
7896    };
7897    if !raw.contains("::") {
7898        return vec![format!("{namespace}::{raw}")];
7899    }
7900    // fqname-M4: peeks at the raw first `::`-split token, including the empty
7901    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
7902    // shape as rust's `rust_reference_looks_external`); the shared structured
7903    // splitter filters empty segments, which would shift "which token is
7904    // first" for that one lead-`::` shape and is not proven equivalent here.
7905    if raw
7906        .split("::")
7907        .next()
7908        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
7909    {
7910        vec![format!("{namespace}::{raw}"), raw]
7911    } else {
7912        vec![raw]
7913    }
7914}
7915
7916fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7917    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
7918}
7919
7920/// Whether a name leaf the field scans reached sits in a value position.
7921///
7922/// Every kind those scans admit already names a value except `type_identifier`.
7923/// tree-sitter spells a non-type template argument with type syntax, so the
7924/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
7925/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
7926/// Admit that leaf only where no visible type explains the spelling, which is
7927/// the order forward navigation applies before it reads the leaf in the value
7928/// namespace (#2556). Every other `type_identifier` is a type reference and
7929/// belongs to the type scan.
7930fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7931    node.kind() != "type_identifier"
7932        || (is_type_shaped_template_argument_name(node)
7933            && ctx
7934                .visibility
7935                .resolve_type(ctx.file, node_text(node, ctx.source))
7936                .is_none())
7937}
7938
7939fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7940    if is_c_offsetof_member_node(node) {
7941        return;
7942    }
7943    if matches!(node.kind(), "identifier" | "field_identifier")
7944        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
7945            .is_some()
7946    {
7947        return;
7948    }
7949    if !matches!(
7950        node.kind(),
7951        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
7952    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7953        || !scan_leaf_is_value_position(node, ctx)
7954        || is_declaration_name(node)
7955        || is_member_field_own_declarator(node, ctx)
7956        || is_selected_field_expression_member_descendant(node)
7957        || is_nested_in_qualified_identifier(node)
7958    {
7959        return;
7960    }
7961    *ctx.raw_match_count += 1;
7962    if global_field_resolves_to_target(node, ctx) {
7963        push_hit(node, ctx);
7964    } else if global_field_is_known_non_target(node, ctx) {
7965    } else {
7966        push_unproven_hit(node, ctx);
7967    }
7968}
7969
7970/// Whether `node` belongs to the selected-member side of any enclosing field
7971/// expression. A reference may be nested arbitrarily inside the receiver side
7972/// (for example, an argument to a call-built fluent receiver), so direct child
7973/// equality is insufficient: classify each ancestor by structured subtree
7974/// containment instead.
7975fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
7976    let candidate = node;
7977    while let Some(parent) = node.parent() {
7978        if parent.kind() == "field_expression" {
7979            if let Some(field) = parent.child_by_field_name("field")
7980                && node_is_within(field, candidate)
7981            {
7982                // The `template` disambiguator in `receiver.template f<Arg>()`
7983                // wraps the selected member in a `dependent_name` whose single
7984                // named child is the `template_*` node the plain spelling puts
7985                // directly under `field`. Unwrap it so both spellings reach the
7986                // same name/arguments split (#2196).
7987                let selected = match field.kind() {
7988                    "dependent_name" => field.named_child(0).unwrap_or(field),
7989                    _ => field,
7990                };
7991                let selected_name = match selected.kind() {
7992                    "template_method" | "template_function" | "template_type" => {
7993                        selected.child_by_field_name("name").unwrap_or(selected)
7994                    }
7995                    _ => selected,
7996                };
7997                if node_is_within(selected_name, candidate) {
7998                    return true;
7999                }
8000                // A template argument is structurally inside the field subtree,
8001                // but it is an independent reference rather than the selected
8002                // member name.
8003                node = parent;
8004                continue;
8005            }
8006            let receiver = parent
8007                .child_by_field_name("argument")
8008                .or_else(|| parent.child_by_field_name("object"))
8009                .or_else(|| parent.named_child(0));
8010            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8011                // Unknown grammar shape inside a field expression: fail closed
8012                // rather than treating it as a receiver reference.
8013                return true;
8014            }
8015        }
8016        node = parent;
8017    }
8018    false
8019}
8020
8021fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8022    let text = node_text(node, ctx.source);
8023    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8024        return false;
8025    }
8026    if text.contains("::") {
8027        return ctx.visibility.contains_named_symbol(
8028            ctx.file,
8029            text,
8030            TargetKind::GlobalField,
8031            &ctx.spec.target,
8032        );
8033    }
8034    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8035        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8036    {
8037        return ctx.visibility.contains_named_symbol(
8038            ctx.file,
8039            text,
8040            TargetKind::GlobalField,
8041            &ctx.spec.target,
8042        );
8043    }
8044    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8045        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8046            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8047                brokk_bifrost_core::analyzer::Language::Cpp,
8048                &namespace,
8049            ) == indexed_scope
8050        })
8051    {
8052        return ctx.visibility.contains_named_symbol(
8053            ctx.file,
8054            text,
8055            TargetKind::GlobalField,
8056            &ctx.spec.target,
8057        );
8058    }
8059    bare_global_field_uniquely_resolves_to_target(text, ctx)
8060}
8061
8062fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8063    let mut matched_target = false;
8064    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8065        if !has_persisted_global_field_identity(unit)
8066            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8067        {
8068            continue;
8069        }
8070        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8071            continue;
8072        }
8073        if same_visible_global_field_symbol(
8074            &ctx.analyzer,
8075            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8076            unit,
8077            &ctx.spec.target,
8078        ) {
8079            matched_target = true;
8080        } else {
8081            return false;
8082        }
8083    }
8084    matched_target
8085}
8086
8087pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8088    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8089    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8090    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8091    // analyzer for every same-named candidate's parent.
8092    unit.is_field() && !unit.short_name().contains('.')
8093}
8094
8095fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8096    let text = node_text(node, ctx.source);
8097    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8098        return true;
8099    }
8100    if text.contains("::") {
8101        return ctx.visibility.resolve_known_non_target(
8102            ctx.file,
8103            text,
8104            TargetKind::GlobalField,
8105            &ctx.spec.target,
8106        );
8107    }
8108    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8109        return false;
8110    };
8111    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8112        && ctx
8113            .visibility
8114            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8115            .any(|unit| {
8116                has_persisted_global_field_identity(unit)
8117                    && unit.identifier() == ctx.spec.member_name
8118                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8119                    && !same_visible_global_field_symbol(
8120                        &ctx.analyzer,
8121                        &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8122                        unit,
8123                        &ctx.spec.target,
8124                    )
8125            })
8126}
8127
8128fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8129    if is_c_offsetof_member_node(node) {
8130        maybe_record_c_offsetof_field_hit(node, ctx);
8131        return;
8132    }
8133    if node.kind() == "field_expression" {
8134        let Some(field) = node.child_by_field_name("field") else {
8135            return;
8136        };
8137        if node_text(field, ctx.source) != ctx.spec.member_name {
8138            return;
8139        }
8140        *ctx.raw_match_count += 1;
8141        let receiver = node
8142            .child_by_field_name("argument")
8143            .or_else(|| node.child_by_field_name("object"));
8144        let receiver_resolution =
8145            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8146        match receiver_resolution {
8147            Some(MethodReceiverTargetResolution::Target)
8148                if !ctx.analyzer.reference_uses_c_semantics(ctx.file)
8149                    || ctx.visibility.declaration_visible_at_reference(
8150                        &ctx.analyzer,
8151                        ctx.file,
8152                        &ctx.spec.target,
8153                        field,
8154                    ) =>
8155            {
8156                push_hit(field, ctx)
8157            }
8158            Some(MethodReceiverTargetResolution::Target) => {}
8159            Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
8160            Some(
8161                MethodReceiverTargetResolution::NonTarget
8162                | MethodReceiverTargetResolution::Ambiguous,
8163            ) => {}
8164        }
8165        return;
8166    }
8167
8168    if matches!(node.kind(), "identifier" | "field_identifier")
8169        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8170        && let Some(designator_owner) =
8171            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8172    {
8173        *ctx.raw_match_count += 1;
8174        match designator_owner {
8175            DesignatedInitializerOwner::Resolved(owner)
8176                if ctx
8177                    .spec
8178                    .owner
8179                    .as_ref()
8180                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8181            {
8182                push_hit(node, ctx);
8183            }
8184            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8185            DesignatedInitializerOwner::Resolved(_) => {}
8186        }
8187        return;
8188    }
8189
8190    let qualified_member_name_matches =
8191        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8192            && cpp_name_component_nodes(node)
8193                .and_then(|components| components.last().copied())
8194                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8195    if !matches!(
8196        node.kind(),
8197        "identifier"
8198            | "field_identifier"
8199            | "qualified_identifier"
8200            | "scoped_identifier"
8201            | "type_identifier"
8202    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8203        && !qualified_member_name_matches)
8204        || !scan_leaf_is_value_position(node, ctx)
8205        || is_declaration_name(node)
8206        || is_member_field_own_declarator(node, ctx)
8207        || is_selected_field_expression_member_descendant(node)
8208        || is_nested_in_qualified_identifier(node)
8209    {
8210        return;
8211    }
8212    *ctx.raw_match_count += 1;
8213    if is_structurally_qualified(node) {
8214        match qualified_owner_resolution(node, ctx) {
8215            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8216            QualifiedOwnerResolution::NonTarget => {}
8217            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8218        }
8219        return;
8220    }
8221    let text = node_text(node, ctx.source);
8222    if ctx.local_shadows.is_shadowed(text) {
8223        return;
8224    }
8225    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8226        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8227    let owner_context = structured_owner_context_resolution(node, ctx);
8228    if matches!(
8229        owner_context,
8230        StructuredOwnerContextResolution::SelfTarget
8231            | StructuredOwnerContextResolution::InheritedTarget
8232    ) || unscoped_enum_match
8233    {
8234        push_hit(node, ctx);
8235    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8236        .then_some(ctx.spec.owner.as_ref())
8237        .flatten()
8238    {
8239        let resolution =
8240            match resolve_active_using_enum_member(node, ctx) {
8241                ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8242                ActiveUsingEnumMemberResolution::Class(resolution) => {
8243                    if direct_class_member_shadows(node, ctx) {
8244                        return;
8245                    }
8246                    resolution
8247                }
8248                ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8249                    if let Some(owner) = structured_enclosing_owner(node, ctx) {
8250                        if direct_class_member_shadows(node, ctx) {
8251                            return;
8252                        }
8253                        let complete_same_file_leaf =
8254                            owner.source() == ctx.file
8255                                && ctx.analyzer.type_hierarchy_provider().is_some_and(
8256                                    |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
8257                                );
8258                        if !complete_same_file_leaf {
8259                            push_unproven_hit(node, ctx);
8260                            return;
8261                        }
8262                    }
8263                    match owner_context {
8264                        StructuredOwnerContextResolution::SelfTarget
8265                        | StructuredOwnerContextResolution::InheritedTarget
8266                        | StructuredOwnerContextResolution::NonTarget => return,
8267                        StructuredOwnerContextResolution::Ambiguous => {
8268                            push_unproven_hit(node, ctx);
8269                            return;
8270                        }
8271                        StructuredOwnerContextResolution::Missing => {}
8272                    }
8273                    if namespace_value_shadows(node, ctx) {
8274                        return;
8275                    }
8276                    resolution
8277                }
8278                ActiveUsingEnumMemberResolution::Missing => {
8279                    if direct_class_member_shadows(node, ctx)
8280                        || (structured_enclosing_owner(node, ctx).is_none()
8281                            && namespace_value_shadows(node, ctx))
8282                    {
8283                        return;
8284                    }
8285                    UsingEnumMemberResolution::Missing
8286                }
8287            };
8288        match resolution {
8289            UsingEnumMemberResolution::Resolved { owner, member }
8290                if same_visible_symbol(&owner, target_owner)
8291                    && same_visible_symbol(&member, &ctx.spec.target) =>
8292            {
8293                push_hit(node, ctx);
8294            }
8295            UsingEnumMemberResolution::Resolved { .. } => {}
8296            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8297                push_unproven_hit(node, ctx)
8298            }
8299        }
8300    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8301        push_unproven_hit(node, ctx);
8302    }
8303}
8304
8305fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8306    if node_text(node, ctx.source) != ctx.spec.member_name {
8307        return;
8308    }
8309    *ctx.raw_match_count += 1;
8310    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8311        push_unproven_hit(node, ctx);
8312        return;
8313    };
8314    let owner = match resolve_type_node_lexically(
8315        type_reference,
8316        &ctx.analyzer,
8317        ctx.visibility,
8318        &ctx.ordinary_type_imports,
8319        ctx.file,
8320        ctx.source,
8321    ) {
8322        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8323        LexicalTypeResolution::Resolved { .. }
8324        | LexicalTypeResolution::Ambiguous
8325        | LexicalTypeResolution::Missing => {
8326            push_unproven_hit(member, ctx);
8327            return;
8328        }
8329    };
8330    let candidates = ctx
8331        .visibility
8332        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8333        .into_iter()
8334        .filter(|candidate| candidate.is_field())
8335        .collect::<Vec<_>>();
8336    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8337        push_hit(member, ctx);
8338    } else if candidates.len() > 1 {
8339        push_unproven_hit(member, ctx);
8340    }
8341}
8342
8343enum ActiveUsingEnumMemberResolution {
8344    Block(UsingEnumMemberResolution),
8345    Class(UsingEnumMemberResolution),
8346    Namespace(UsingEnumMemberResolution),
8347    Missing,
8348}
8349
8350fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8351    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8352        ctx.visibility
8353            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8354            .into_iter()
8355            .next()
8356            .is_some()
8357    })
8358}
8359
8360fn resolve_active_using_enum_member(
8361    node: Node<'_>,
8362    ctx: &ScanCtx<'_>,
8363) -> ActiveUsingEnumMemberResolution {
8364    let block =
8365        ctx.using_enum_owners
8366            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8367    if !matches!(block, UsingEnumMemberResolution::Missing) {
8368        return ActiveUsingEnumMemberResolution::Block(block);
8369    }
8370    let class = structured_enclosing_owner(node, ctx);
8371    let namespace = enclosing_namespace_components(node, ctx.source);
8372    match ctx.semantic_using_enum_owners.resolve_member(
8373        ctx.visibility,
8374        ctx.file,
8375        class.as_ref(),
8376        &namespace,
8377        node.start_byte(),
8378        &ctx.spec.member_name,
8379    ) {
8380        SemanticUsingEnumMemberResolution::Class(resolution) => {
8381            ActiveUsingEnumMemberResolution::Class(resolution)
8382        }
8383        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8384            ActiveUsingEnumMemberResolution::Namespace(resolution)
8385        }
8386        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8387    }
8388}
8389
8390fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8391    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8392    !matches!(
8393        resolve_namespace_value(
8394            &ctx.analyzer,
8395            ctx.visibility,
8396            ctx.file,
8397            &namespace,
8398            &ctx.spec.member_name,
8399            node.start_byte(),
8400        ),
8401        NamespaceValueResolution::Missing
8402    )
8403}
8404
8405/// Whether `node` is a component of an enclosing qualified identifier's own
8406/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8407/// reference surfaced for it.
8408///
8409/// A qualified identifier owns only that path. Everything else beneath it is an
8410/// independent reference that its own target scanner must resolve:
8411///
8412/// - `Owner::Template<argument>` holds each template argument outside the path.
8413/// - Error recovery can hang a complete member initializer off an `ERROR` child
8414///   of a synthetic qualified identifier, where no structured path exists.
8415/// - The grammar accepts a `pointer_type_declarator` as a qualified
8416///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8417///   folds a whole parameter list under one `qualified_identifier`. Parameter
8418///   default values there are ordinary value expressions (#2548).
8419///
8420/// Follow the structured `scope`/`name` links rather than every ancestor, which
8421/// covers all three cases with one rule.
8422fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8423    if node.kind() == "qualified_identifier" {
8424        return false;
8425    }
8426    let mut current = node;
8427    while let Some(parent) = current.parent() {
8428        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8429            parent
8430                .child_by_field_name(field)
8431                .is_some_and(|component| same_node(component, current))
8432        });
8433        if !on_name_path {
8434            return false;
8435        }
8436        if parent.kind() == "qualified_identifier" {
8437            return true;
8438        }
8439        current = parent;
8440    }
8441    false
8442}
8443
8444fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8445    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8446}
8447
8448/// What a receiver spelled as a qualified name denotes.
8449///
8450/// `ns::value.run()` reads the member off the declared type of the variable
8451/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8452/// Forward navigation and the inverse walk both ask this before typing such a
8453/// receiver, so the two directions accept the same receiver spellings (#2547).
8454pub enum QualifiedReceiverBase {
8455    /// Every visible declaration of the one variable the path names.
8456    Variable(Vec<CodeUnit>),
8457    /// The type the path names when it names no variable.
8458    Type(Option<CodeUnit>),
8459    /// The path names more than one variable; typing either would be a guess.
8460    Ambiguous,
8461}
8462
8463pub fn qualified_receiver_base(
8464    visibility: &VisibilityIndex<'_>,
8465    file: &ProjectFile,
8466    receiver: Node<'_>,
8467    source: &str,
8468) -> QualifiedReceiverBase {
8469    let reference = node_text(receiver, source);
8470    let fields = visibility
8471        .named_candidates(file, reference, TargetKind::GlobalField)
8472        .into_iter()
8473        .filter(has_persisted_global_field_identity)
8474        .collect::<Vec<_>>();
8475    let Some(first) = fields.first() else {
8476        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8477    };
8478    if fields
8479        .iter()
8480        .skip(1)
8481        .any(|field| !same_visible_symbol(first, field))
8482    {
8483        return QualifiedReceiverBase::Ambiguous;
8484    }
8485    QualifiedReceiverBase::Variable(fields)
8486}
8487
8488fn receiver_type_units_with_budget(
8489    node: Node<'_>,
8490    source: &str,
8491    ctx: &ScanCtx<'_>,
8492    remaining_call_depth: usize,
8493) -> Vec<CodeUnit> {
8494    let mut current = node;
8495    let mut member_chain = Vec::new();
8496    let mut base_units = loop {
8497        match current.kind() {
8498            "field_expression" => {
8499                let Some(member) = current.child_by_field_name("field") else {
8500                    return Vec::new();
8501                };
8502                let Some(receiver) = current
8503                    .child_by_field_name("argument")
8504                    .or_else(|| current.child_by_field_name("object"))
8505                    .or_else(|| current.named_child(0))
8506                else {
8507                    return Vec::new();
8508                };
8509                member_chain.push(node_text(member, source));
8510                current = receiver;
8511            }
8512            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8513                let Some(inner) = current
8514                    .child_by_field_name("argument")
8515                    .or_else(|| current.named_child(0))
8516                else {
8517                    return Vec::new();
8518                };
8519                current = inner;
8520            }
8521            // Tree-sitter uses `field_identifier` for an unqualified member
8522            // field when it appears as the base of another field expression
8523            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8524            // same structured binding and enclosing-owner paths as an
8525            // ordinary identifier; falling through to `resolve_type` would
8526            // treat the field name as a type and lose the receiver identity.
8527            "identifier" | "field_identifier" => {
8528                let name = node_text(current, source);
8529                // Function-like macro locals are visible in the replacement
8530                // range after substitution and shadow caller bindings. The
8531                // receiver scan has no root field, so recover the source tree
8532                // root before asking the visibility index for that binding.
8533                let mut root = current;
8534                while let Some(parent) = ctx.ancestry.parent(root) {
8535                    root = parent;
8536                }
8537                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8538                    ctx.file,
8539                    root,
8540                    source,
8541                    current.start_byte(),
8542                    current.end_byte(),
8543                ) && binding.name == name
8544                {
8545                    let normalized = normalize_cpp_type_name(&binding.type_name);
8546                    let unit = binding
8547                        .proven_unit
8548                        .clone()
8549                        .or_else(|| {
8550                            binding.type_node.and_then(|type_node| {
8551                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8552                            })
8553                        })
8554                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8555                    // Keep the macro declaration's shadowing boundary even
8556                    // when its type is unavailable: an unresolved macro local
8557                    // cannot be reinterpreted as a static type or outer value.
8558                    break unit.into_iter().collect();
8559                }
8560                let local = ctx.bindings.resolve_symbol(name);
8561                if let Some(bindings) = local.as_precise() {
8562                    break receiver_units_from_bindings(current, bindings, ctx);
8563                }
8564                if ctx.bindings.is_shadowed(name) {
8565                    return Vec::new();
8566                }
8567                let owner = structured_enclosing_owner(current, ctx)
8568                    .filter(CodeUnit::is_class)
8569                    .or_else(|| {
8570                        enclosing_context(current, ctx)
8571                            .owner
8572                            .filter(CodeUnit::is_class)
8573                    });
8574                if let Some(owner) = owner {
8575                    // The enclosing class is only the search root: an implicit
8576                    // member-field receiver can be declared on any base, so the
8577                    // declaring owner comes from the same hierarchy walk the
8578                    // member chain below uses, not from an exact-parent match.
8579                    let declaring_owner = match resolve_declaring_member_owner(
8580                        &ctx.analyzer,
8581                        ctx.visibility,
8582                        ctx.file,
8583                        &owner,
8584                        name,
8585                    ) {
8586                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8587                        EnclosingMemberOwnerResolution::Missing => None,
8588                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8589                    };
8590                    if let Some(declaring_owner) = declaring_owner {
8591                        let implicit_fields = ctx
8592                            .visibility
8593                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8594                            .into_iter()
8595                            .filter(|unit| unit.is_field())
8596                            .collect::<Vec<_>>();
8597                        if !implicit_fields.is_empty() {
8598                            break receiver_units_from_declared_fields(
8599                                implicit_fields,
8600                                current,
8601                                ctx,
8602                            );
8603                        }
8604                    }
8605                }
8606                let global_fields = ctx
8607                    .visibility
8608                    .visible_identifier_candidates(ctx.file, name)
8609                    .filter(|unit| {
8610                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8611                    })
8612                    .collect::<Vec<_>>();
8613                if global_fields.is_empty() {
8614                    break ctx
8615                        .visibility
8616                        .resolve_type(ctx.file, name)
8617                        .into_iter()
8618                        .collect();
8619                }
8620                if let Some(first) = global_fields.first()
8621                    && global_fields.iter().skip(1).any(|field| {
8622                        !same_visible_global_field_symbol(
8623                            &ctx.analyzer,
8624                            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8625                            first,
8626                            field,
8627                        )
8628                    })
8629                {
8630                    return Vec::new();
8631                }
8632                break receiver_units_from_declared_fields(global_fields, current, ctx);
8633            }
8634            "call_expression" | "new_expression" => {
8635                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8636                    .and_then(|binding| binding.unit)
8637                    .into_iter()
8638                    .collect();
8639            }
8640            // `((T*)p)->member` is typed by what the cast spells, not by the
8641            // operand it converts (#2981). Pointer depth does not change which
8642            // type declares the member, so only the descriptor's type matters.
8643            "cast_expression" => {
8644                let Some(descriptor) = current.child_by_field_name("type") else {
8645                    return Vec::new();
8646                };
8647                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8648                    return Vec::new();
8649                };
8650                break unit.into_iter().collect();
8651            }
8652            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8653                let name = node_text(current, source);
8654                let local = ctx.bindings.resolve_symbol(name);
8655                if let Some(bindings) = local.as_precise() {
8656                    break receiver_units_from_bindings(current, bindings, ctx);
8657                }
8658                return Vec::new();
8659            }
8660            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8661            "qualified_identifier" | "scoped_identifier" => {
8662                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8663                    QualifiedReceiverBase::Variable(fields) => {
8664                        break receiver_units_from_declared_fields(
8665                            fields.iter().collect(),
8666                            current,
8667                            ctx,
8668                        );
8669                    }
8670                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
8671                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
8672                }
8673            }
8674            _ => {
8675                break ctx
8676                    .visibility
8677                    .resolve_type(ctx.file, node_text(current, source))
8678                    .into_iter()
8679                    .collect();
8680            }
8681        }
8682    };
8683
8684    base_units = canonical_receiver_units(base_units, ctx);
8685    if base_units.is_empty() {
8686        return Vec::new();
8687    }
8688
8689    while let Some(member_name) = member_chain.pop() {
8690        let mut next_units = Vec::new();
8691        for owner in &base_units {
8692            let declaring_owner = match resolve_declaring_member_owner(
8693                &ctx.analyzer,
8694                ctx.visibility,
8695                ctx.file,
8696                owner,
8697                member_name,
8698            ) {
8699                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8700                EnclosingMemberOwnerResolution::Missing => continue,
8701                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8702            };
8703            let fields = ctx.visibility.visible_members_for_owner_name(
8704                ctx.file,
8705                &declaring_owner,
8706                member_name,
8707            );
8708            for field in fields.into_iter().filter(|unit| unit.is_field()) {
8709                let Some(unit) =
8710                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8711                        .and_then(|binding| binding.unit)
8712                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
8713                else {
8714                    continue;
8715                };
8716                if !next_units
8717                    .iter()
8718                    .any(|existing| same_visible_symbol(existing, &unit))
8719                {
8720                    next_units.push(unit);
8721                }
8722            }
8723        }
8724        if next_units.is_empty() {
8725            return Vec::new();
8726        }
8727        base_units = unanimous_receiver_units(next_units);
8728        if base_units.is_empty() {
8729            return Vec::new();
8730        }
8731    }
8732    base_units
8733}
8734
8735fn receiver_units_from_bindings(
8736    node: Node<'_>,
8737    bindings: &HashSet<CppScanBinding>,
8738    ctx: &ScanCtx<'_>,
8739) -> Vec<CodeUnit> {
8740    let mut units = Vec::new();
8741    for binding in bindings {
8742        let raw_unit = if let Some(unit) = &binding.unit {
8743            unit.clone()
8744        } else {
8745            let Some(type_name) = binding.type_name.as_deref() else {
8746                return Vec::new();
8747            };
8748            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
8749                return Vec::new();
8750            };
8751            unit
8752        };
8753        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
8754            // Type aliases are represented as class units. When an alias has
8755            // a dependent target, canonicalization deliberately preserves
8756            // that alias rather than inventing a concrete class. Give the
8757            // existing target-guided structured recovery a chance to prove
8758            // the queried owner before accepting that unresolved identity.
8759            if same_visible_symbol(&unit, &raw_unit)
8760                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
8761            {
8762                units.push(recovered);
8763                continue;
8764            }
8765            units.push(unit);
8766            continue;
8767        }
8768        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
8769            units.push(unit);
8770            continue;
8771        }
8772        return Vec::new();
8773    }
8774    unanimous_receiver_units(units)
8775}
8776
8777/// Resolve a using-alias receiver from its declaration's structured RHS when
8778/// the alias target index cannot cross a malformed namespace-sentinel node.
8779/// The inverse target owner supplies only the exact class identity to prove;
8780/// lexical AST resolution still decides whether the alias denotes that class.
8781fn recovered_receiver_alias_target(
8782    reference: Node<'_>,
8783    alias: &CodeUnit,
8784    ctx: &ScanCtx<'_>,
8785) -> Option<CodeUnit> {
8786    if !ctx
8787        .analyzer
8788        .type_alias_provider()
8789        .is_some_and(|provider| provider.is_type_alias(alias))
8790    {
8791        return None;
8792    }
8793    let target = ctx.spec.owner.as_ref()?.clone();
8794    if !target.is_class() || alias.source() != ctx.file {
8795        return None;
8796    }
8797    let range = ctx
8798        .analyzer
8799        .ranges(alias)
8800        .into_iter()
8801        .find(|range| range.start_byte < range.end_byte)?;
8802    let mut node =
8803        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8804    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
8805        node = ctx.ancestry.parent(node)?;
8806    }
8807    let type_descriptor = node.child_by_field_name("type")?;
8808    let type_node = receiver_type_node_base(type_descriptor);
8809    let resolution = resolve_type_node_lexically_for_target(
8810        type_node,
8811        &ctx.analyzer,
8812        ctx.visibility,
8813        &ctx.ordinary_type_imports,
8814        ctx.file,
8815        ctx.source,
8816        &target,
8817        Some(&ctx.lexical_scope_cache),
8818        ctx.recovered_sentinel_scope(type_node).as_deref(),
8819    );
8820    if let LexicalTypeResolution::Resolved {
8821        unit, candidates, ..
8822    } = resolution
8823        && (same_visible_symbol(&unit, &target)
8824            || candidates
8825                .iter()
8826                .any(|candidate| same_visible_symbol(candidate, &target)))
8827    {
8828        return Some(target);
8829    }
8830    let (components, global) = type_reference_components(type_node, ctx.source)?;
8831    if !global
8832        && components.len() == 2
8833        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
8834        && dependent_nested_alias_concrete_target(
8835            &ctx.analyzer,
8836            ctx.visibility,
8837            ctx.file,
8838            alias,
8839            &components[1],
8840            type_node,
8841        )
8842        .is_some_and(|unit| same_visible_symbol(&unit, &target))
8843    {
8844        return Some(target);
8845    }
8846    let scope = ctx
8847        .recovered_sentinel_scope(type_node)
8848        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8849    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
8850    let visible = ctx.visibility.external_type_candidate_visible_in_context(
8851        &ctx.analyzer,
8852        ctx.file,
8853        &target,
8854        type_node,
8855    );
8856    (path_matches && visible).then_some(target)
8857}
8858
8859/// The one concrete class that `alias`, spelled `Param::nested_name` through an
8860/// active template type parameter `Param`, can name: every other visible
8861/// class-owned alias named `nested_name` must reach the same indexed class
8862/// (#2250). `type_node` is the alias's target type node, whose guard context
8863/// each candidate must be compatible with. Competing concrete targets answer
8864/// `None`.
8865pub fn dependent_nested_alias_concrete_target(
8866    analyzer: &CppGraphSource<'_>,
8867    visibility: &VisibilityIndex<'_>,
8868    file: &ProjectFile,
8869    alias: &CodeUnit,
8870    nested_name: &str,
8871    type_node: Node<'_>,
8872) -> Option<CodeUnit> {
8873    let alias_provider = analyzer.type_alias_provider()?;
8874    let concrete = visibility
8875        .visible_identifier_candidates(file, nested_name)
8876        .filter(|candidate| {
8877            alias_provider.is_type_alias(candidate)
8878                && !same_visible_symbol(candidate, alias)
8879                && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
8880                && visibility.is_physically_visible(file, candidate)
8881                && visibility.external_type_candidate_guard_compatible_in_context(
8882                    analyzer, file, candidate, type_node,
8883                )
8884        })
8885        .filter_map(|candidate| {
8886            let canonical =
8887                visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
8888            // Another dependent alias can have the same nested name but
8889            // still canonicalize only to itself. It supplies no concrete
8890            // receiver identity and therefore cannot compete with an
8891            // alias that reaches an indexed class.
8892            (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
8893        })
8894        .collect::<Vec<_>>();
8895    match unanimous_receiver_units(concrete).as_slice() {
8896        [unit] => Some(unit.clone()),
8897        _ => None,
8898    }
8899}
8900
8901fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8902    let normalized = normalize_cpp_type_name(type_name);
8903    if normalized.is_empty() {
8904        return None;
8905    }
8906
8907    // A function-local alias is intentionally absent from the visibility
8908    // index. Recover its RHS from the structured alias declaration before
8909    // trying file-visible type lookup; this keeps the alias's lexical shadow
8910    // boundary intact.
8911    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
8912        match resolve_receiver_type_node(alias_type, ctx) {
8913            Ok(Some(unit)) => return Some(unit),
8914            Err(_) => return None,
8915            Ok(None) => {}
8916        }
8917    }
8918
8919    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
8920        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
8921        LexicalTypeResolution::Ambiguous => return None,
8922        LexicalTypeResolution::Missing => {}
8923    }
8924    let candidates = ctx
8925        .visibility
8926        .type_name_candidates(ctx.file, &normalized)
8927        .into_iter()
8928        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
8929        .collect();
8930    unanimous_receiver_units(candidates).into_iter().next()
8931}
8932
8933/// The receiver type a type node names: the visible indexed type it resolves
8934/// to, else the type its components name in the enclosing lexical scope.
8935///
8936/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
8937/// is unwrapped to its type specifier first, so every caller that has a spelled
8938/// type reads it the same way. `Err` reports a template whose arguments could
8939/// not be resolved; that is a structured failure, not an absent type.
8940fn resolve_receiver_type_node(
8941    type_node: Node<'_>,
8942    ctx: &ScanCtx<'_>,
8943) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
8944    let type_node = receiver_type_node_base(type_node);
8945    if let Some(unit) = ctx
8946        .visibility
8947        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
8948    {
8949        return Ok(Some(unit));
8950    }
8951    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
8952}
8953
8954fn resolve_receiver_type_node_lexically(
8955    type_node: Node<'_>,
8956    ctx: &ScanCtx<'_>,
8957) -> Option<CodeUnit> {
8958    let type_node = receiver_type_node_base(type_node);
8959    let components = cpp_type_name_components(type_node, ctx.source)?;
8960    let lexical_scope = match enclosing_lexical_scope_components(
8961        type_node,
8962        &ctx.analyzer,
8963        ctx.visibility,
8964        ctx.file,
8965        ctx.source,
8966    ) {
8967        LexicalScopeResolution::Resolved(scope) => scope,
8968        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
8969    };
8970    match ctx.visibility.resolve_type_components_lexically(
8971        &ctx.analyzer,
8972        ctx.file,
8973        &components,
8974        is_globally_qualified_cpp_name(type_node),
8975        &lexical_scope,
8976    ) {
8977        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
8978        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
8979    }
8980}
8981
8982fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
8983    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
8984        let Some(inner) = node.child_by_field_name("type").or_else(|| {
8985            if node.kind() == "dependent_type" {
8986                node.named_child(0)
8987            } else {
8988                None
8989            }
8990        }) else {
8991            break;
8992        };
8993        node = inner;
8994    }
8995    node
8996}
8997
8998fn resolve_receiver_type_name_lexically(
8999    node: Node<'_>,
9000    normalized: &str,
9001    ctx: &ScanCtx<'_>,
9002) -> LexicalTypeResolution {
9003    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9004        brokk_bifrost_core::analyzer::Language::Cpp,
9005        normalized,
9006    );
9007    if components.is_empty() {
9008        return LexicalTypeResolution::Missing;
9009    }
9010    let lexical_scope = match enclosing_lexical_scope_components(
9011        node,
9012        &ctx.analyzer,
9013        ctx.visibility,
9014        ctx.file,
9015        ctx.source,
9016    ) {
9017        LexicalScopeResolution::Resolved(scope) => scope,
9018        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9019        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9020    };
9021    ctx.visibility.resolve_type_components_lexically(
9022        &ctx.analyzer,
9023        ctx.file,
9024        &components,
9025        normalized.starts_with("::"),
9026        &lexical_scope,
9027    )
9028}
9029
9030fn local_receiver_alias_type_node<'tree>(
9031    node: Node<'tree>,
9032    name: &str,
9033    ctx: &ScanCtx<'_>,
9034) -> Option<Node<'tree>> {
9035    let callable = nearest_callable_scope(node)?;
9036    let mut root_callable = callable;
9037    // See `target_guided_nested_type_terminal_hit`: the result carries the
9038    // caller's tree lifetime.
9039    let mut ancestor = callable.parent();
9040    while let Some(current) = ancestor {
9041        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9042            root_callable = current;
9043        }
9044        ancestor = current.parent();
9045    }
9046
9047    let mut stack = vec![root_callable];
9048    let mut best = None;
9049    while let Some(current) = stack.pop() {
9050        if current.start_byte() >= node.start_byte() {
9051            continue;
9052        }
9053        if local_type_alias_name_node(current)
9054            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9055            && local_alias_scope_contains_node(current, node)
9056        {
9057            let replace = best
9058                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9059            if replace {
9060                best = current.child_by_field_name("type");
9061            }
9062        }
9063        let mut cursor = current.walk();
9064        stack.extend(current.named_children(&mut cursor));
9065    }
9066    best
9067}
9068
9069fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9070    let mut canonical = Vec::with_capacity(units.len());
9071    for unit in units {
9072        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9073            return Vec::new();
9074        };
9075        canonical.push(unit);
9076    }
9077    unanimous_receiver_units(canonical)
9078}
9079
9080fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9081    if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
9082        return cached.clone();
9083    }
9084    let canonical = ctx
9085        .visibility
9086        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9087    ctx.receiver_canonical_type_cache
9088        .borrow_mut()
9089        .insert(unit.clone(), canonical.clone());
9090    canonical
9091}
9092
9093fn receiver_units_from_declared_fields(
9094    fields: Vec<&CodeUnit>,
9095    reference: Node<'_>,
9096    ctx: &ScanCtx<'_>,
9097) -> Vec<CodeUnit> {
9098    let Some(first) = fields.first() else {
9099        return Vec::new();
9100    };
9101    if fields
9102        .iter()
9103        .skip(1)
9104        .any(|field| !same_visible_symbol(first, field))
9105    {
9106        return Vec::new();
9107    }
9108    unanimous_receiver_units(
9109        fields
9110            .into_iter()
9111            .filter_map(|field| {
9112                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9113                    .and_then(|binding| binding.unit)
9114                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9115            })
9116            .collect(),
9117    )
9118}
9119
9120/// Resolve a field receiver's declared type from its structured declaration
9121/// when the persisted type fact was built under a malformed sentinel scope.
9122/// The queried member owner supplies the exact class identity to prove; the
9123/// declaration's type node and recovered lexical path provide the evidence.
9124fn recovered_receiver_field_type(
9125    reference: Node<'_>,
9126    field: &CodeUnit,
9127    ctx: &ScanCtx<'_>,
9128) -> Option<CodeUnit> {
9129    let target = ctx.spec.owner.as_ref()?.clone();
9130    if !target.is_class() || field.source() != ctx.file {
9131        return None;
9132    }
9133    let range = ctx
9134        .analyzer
9135        .ranges(field)
9136        .into_iter()
9137        .find(|range| range.start_byte < range.end_byte)?;
9138    let mut declaration =
9139        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9140    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9141        declaration = ctx.ancestry.parent(declaration)?;
9142    }
9143    let type_node = first_type_child(declaration)?;
9144    let resolution = resolve_type_node_lexically_for_target(
9145        type_node,
9146        &ctx.analyzer,
9147        ctx.visibility,
9148        &ctx.ordinary_type_imports,
9149        ctx.file,
9150        ctx.source,
9151        &target,
9152        Some(&ctx.lexical_scope_cache),
9153        ctx.recovered_sentinel_scope(type_node).as_deref(),
9154    );
9155    if let LexicalTypeResolution::Resolved {
9156        unit, candidates, ..
9157    } = resolution
9158        && (same_visible_symbol(&unit, &target)
9159            || candidates
9160                .iter()
9161                .any(|candidate| same_visible_symbol(candidate, &target)))
9162    {
9163        return Some(target);
9164    }
9165    let type_node = receiver_type_node_base(type_node);
9166    let (components, global) = type_reference_components(type_node, ctx.source)?;
9167    let scope = ctx
9168        .recovered_sentinel_scope(type_node)
9169        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9170    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9171        && ctx.visibility.external_type_candidate_visible_in_context(
9172            &ctx.analyzer,
9173            ctx.file,
9174            &target,
9175            type_node,
9176        ))
9177    .then_some(target)
9178}
9179
9180fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9181    let mut unique = Vec::new();
9182    for unit in units {
9183        if !unique
9184            .iter()
9185            .any(|existing| same_visible_symbol(existing, &unit))
9186        {
9187            unique.push(unit);
9188            if unique.len() > 1 {
9189                return Vec::new();
9190            }
9191        }
9192    }
9193    unique
9194}
9195
9196fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9197    let Some(owner) = ctx.spec.owner.as_ref() else {
9198        return false;
9199    };
9200    match node.kind() {
9201        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9202        // When that structured lookup fails, the base expression's owner is not the field's
9203        // type: treating `value_` as `Value` would misclassify
9204        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9205        // with the `this` node itself, so failing closed for an unresolved field chain does not
9206        // discard genuine self calls.
9207        "field_expression" => false,
9208        "call_expression" => node
9209            .child_by_field_name("function")
9210            .is_some_and(|function| receiver_matches_target(function, ctx)),
9211        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9212            .child_by_field_name("argument")
9213            .or_else(|| node.named_child(0))
9214            .is_some_and(|child| receiver_matches_target(child, ctx)),
9215        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9216            .bindings
9217            .resolve_symbol(node_text(node, ctx.source))
9218            .as_precise()
9219            .is_some_and(|targets| {
9220                targets
9221                    .iter()
9222                    .filter_map(|target| target.unit.as_ref())
9223                    .any(|target| {
9224                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9225                    })
9226            }),
9227        "this" => same_owner_context(node, ctx),
9228        _ => qualified_owner_matches(node, ctx),
9229    }
9230}
9231
9232fn declaring_owner_for_explicit_receiver(
9233    receiver: Node<'_>,
9234    call_arity: Option<usize>,
9235    ctx: &ScanCtx<'_>,
9236) -> EnclosingMemberOwnerResolution {
9237    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9238        return EnclosingMemberOwnerResolution::Missing;
9239    }
9240    declaring_owner_from_receiver_units(
9241        receiver_type_units(receiver, ctx.source, ctx),
9242        receiver.start_byte(),
9243        call_arity,
9244        ctx,
9245    )
9246}
9247
9248/// The owner that declares the queried member for a receiver already typed.
9249///
9250/// `reference_byte` is the reference's position in the scanned file, which
9251/// decides which declarations are visible there. A receiver recovered from a
9252/// macro replacement is typed against the sentinel parse but still reported at
9253/// its bytes in the defining file, so the two arrive separately.
9254fn declaring_owner_from_receiver_units(
9255    receiver_units: Vec<CodeUnit>,
9256    reference_byte: usize,
9257    call_arity: Option<usize>,
9258    ctx: &ScanCtx<'_>,
9259) -> EnclosingMemberOwnerResolution {
9260    let mut declaring_owner = None;
9261    for receiver_owner in receiver_units {
9262        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9263            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9264        }) {
9265            if declaring_owner
9266                .as_ref()
9267                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9268            {
9269                return EnclosingMemberOwnerResolution::Ambiguous;
9270            }
9271            declaring_owner = Some(receiver_owner);
9272            continue;
9273        }
9274        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9275        let owner_resolution = match call_arity {
9276            Some(arity) => resolve_declaring_callable_owner(
9277                &ctx.analyzer,
9278                ctx.visibility,
9279                ctx.file,
9280                ordinary,
9281                &ctx.spec.member_name,
9282                arity,
9283            ),
9284            None => ordinary,
9285        };
9286        match owner_resolution {
9287            EnclosingMemberOwnerResolution::Owner(owner) => {
9288                if declaring_owner
9289                    .as_ref()
9290                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9291                {
9292                    return EnclosingMemberOwnerResolution::Ambiguous;
9293                }
9294                declaring_owner = Some(owner);
9295            }
9296            EnclosingMemberOwnerResolution::Ambiguous => {
9297                return EnclosingMemberOwnerResolution::Ambiguous;
9298            }
9299            EnclosingMemberOwnerResolution::Missing => {}
9300        }
9301    }
9302    declaring_owner
9303        .map(EnclosingMemberOwnerResolution::Owner)
9304        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9305}
9306
9307fn declaring_owner_from_call_function(
9308    function: Node<'_>,
9309    call_arity: Option<usize>,
9310    ctx: &ScanCtx<'_>,
9311) -> Option<EnclosingMemberOwnerResolution> {
9312    match function.kind() {
9313        "field_expression" => function
9314            .child_by_field_name("argument")
9315            .or_else(|| function.child_by_field_name("object"))
9316            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9317            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9318        "call_expression" => function
9319            .child_by_field_name("function")
9320            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9321        _ => None,
9322    }
9323}
9324
9325enum MethodReceiverTargetResolution {
9326    Target,
9327    NonTarget,
9328    Ambiguous,
9329    Missing,
9330}
9331
9332fn method_receiver_target_resolution(
9333    node: Node<'_>,
9334    declaring_owner: EnclosingMemberOwnerResolution,
9335    ctx: &ScanCtx<'_>,
9336) -> MethodReceiverTargetResolution {
9337    match declaring_owner {
9338        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9339            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9340        }
9341        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9342            MethodReceiverTargetResolution::Missing
9343        }
9344        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9345            MethodReceiverTargetResolution::Target
9346        }
9347        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9348            MethodReceiverTargetResolution::NonTarget
9349        }
9350        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9351    }
9352}
9353
9354/// Decide the queried member against a receiver's declaring owner alone.
9355///
9356/// This is the part of receiver typing that needs nothing but the owner and
9357/// the reference's position, so a member call recovered from a macro
9358/// replacement reaches the same verdict as ordinary code even though its
9359/// syntax lives in a separate sentinel parse.
9360fn declaring_owner_target_resolution(
9361    declaring_owner: EnclosingMemberOwnerResolution,
9362    reference_byte: usize,
9363    ctx: &ScanCtx<'_>,
9364) -> MethodReceiverTargetResolution {
9365    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9366        return MethodReceiverTargetResolution::Missing;
9367    };
9368    match declaring_owner {
9369        EnclosingMemberOwnerResolution::Owner(owner)
9370            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9371        {
9372            MethodReceiverTargetResolution::Target
9373        }
9374        EnclosingMemberOwnerResolution::Owner(owner)
9375            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9376        {
9377            MethodReceiverTargetResolution::NonTarget
9378        }
9379        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9380            MethodReceiverTargetResolution::Missing
9381        }
9382        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9383    }
9384}
9385
9386fn explicit_receiver_target_resolution(
9387    receiver: Node<'_>,
9388    call_arity: Option<usize>,
9389    ctx: &ScanCtx<'_>,
9390) -> MethodReceiverTargetResolution {
9391    method_receiver_target_resolution(
9392        receiver,
9393        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9394        ctx,
9395    )
9396}
9397
9398fn call_function_target_resolution(
9399    function: Node<'_>,
9400    ctx: &ScanCtx<'_>,
9401) -> MethodReceiverTargetResolution {
9402    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9403        (call.kind() == "call_expression")
9404            .then(|| {
9405                ctx.visibility
9406                    .call_arity_evidence(ctx.file, call, ctx.source)
9407                    .exact()
9408            })
9409            .flatten()
9410    });
9411    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9412    else {
9413        // A bare function identifier has an implicit receiver. Do not reinterpret
9414        // that identifier as a same-named type or value before enclosing-owner
9415        // lookup gets a chance to establish the member call.
9416        return MethodReceiverTargetResolution::Missing;
9417    };
9418    method_receiver_target_resolution(function, declaring_owner, ctx)
9419}
9420
9421fn receiver_owner_matches_target(
9422    receiver_owner: &CodeUnit,
9423    target_owner: &CodeUnit,
9424    reference_byte: usize,
9425    ctx: &ScanCtx<'_>,
9426) -> bool {
9427    same_symbol(receiver_owner, target_owner)
9428        || same_logical_symbol(receiver_owner, target_owner)
9429            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9430                || (ctx.spec.owner_is_forward_declaration
9431                    && ctx
9432                        .visibility
9433                        .is_physically_visible(ctx.file, receiver_owner))
9434                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9435                || target_group_contains_owner_peer(receiver_owner, ctx))
9436}
9437
9438fn receiver_owner_is_known_non_target(
9439    receiver_owner: &CodeUnit,
9440    target_owner: &CodeUnit,
9441    reference_byte: usize,
9442    ctx: &ScanCtx<'_>,
9443) -> bool {
9444    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9445        return false;
9446    }
9447    if !same_logical_symbol(receiver_owner, target_owner) {
9448        return true;
9449    }
9450    !ctx.target_group.iter().any(|target| {
9451        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9452    })
9453}
9454
9455fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9456    ctx.visibility
9457        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9458        && ctx.target_group.iter().any(|target| {
9459            type_owner_of(&ctx.analyzer, target)
9460                .as_ref()
9461                .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9462        })
9463}
9464
9465fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9466    same_symbol(candidate_owner, owner)
9467        || (same_logical_symbol(candidate_owner, owner)
9468            && candidate_owner.source() == owner.source())
9469}
9470
9471fn visible_target_peer_matches_owner(
9472    owner: &CodeUnit,
9473    reference_byte: usize,
9474    ctx: &ScanCtx<'_>,
9475) -> bool {
9476    ctx.visibility
9477        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9478        && ctx
9479            .visibility
9480            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9481            .any(|candidate| {
9482                cpp_callable_definitions_share_identity_evidence(
9483                    &ctx.analyzer,
9484                    candidate,
9485                    &ctx.spec.target,
9486                ) && ctx.visibility.declaration_visible_at(
9487                    &ctx.analyzer,
9488                    ctx.file,
9489                    candidate,
9490                    reference_byte,
9491                ) && type_owner_of(&ctx.analyzer, candidate)
9492                    .as_ref()
9493                    .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9494            })
9495}
9496
9497fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9498    match function.kind() {
9499        "field_expression" => function
9500            .child_by_field_name("argument")
9501            .or_else(|| function.child_by_field_name("object"))
9502            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9503        _ => receiver_is_self_like(function, reference_is_c),
9504    }
9505}
9506
9507fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9508    let Some(owner) = ctx.spec.owner.as_ref() else {
9509        return false;
9510    };
9511    match node.kind() {
9512        "field_expression" => node
9513            .child_by_field_name("argument")
9514            .or_else(|| node.child_by_field_name("object"))
9515            .is_some_and(|receiver| {
9516                // `this` names the enclosing class, not necessarily the class that
9517                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9518                // lookup decide whether this is a same-owner call, an inherited base
9519                // member, a nearer override, or an ambiguous base. Rejecting a derived
9520                // `this` merely because its immediate type differs from the target owner
9521                // drops genuine inherited calls (#2541).
9522                if receiver_is_self_like(
9523                    receiver,
9524                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9525                ) {
9526                    return false;
9527                }
9528                if receiver_is_known_function_declarator(receiver, ctx) {
9529                    return true;
9530                }
9531                let units = receiver_type_units(receiver, ctx.source, ctx);
9532                !units.is_empty()
9533                    && units.iter().all(|target| {
9534                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9535                    })
9536            }),
9537        "call_expression" => node
9538            .child_by_field_name("function")
9539            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9540        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9541            .child_by_field_name("argument")
9542            .or_else(|| node.named_child(0))
9543            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9544        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9545            .bindings
9546            .resolve_symbol(node_text(node, ctx.source))
9547            .as_precise()
9548            .is_some_and(|targets| {
9549                let units = targets
9550                    .iter()
9551                    .filter_map(|target| target.unit.as_ref())
9552                    .collect::<Vec<_>>();
9553                !units.is_empty()
9554                    && units.iter().all(|target| {
9555                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9556                    })
9557            }),
9558        "this" => known_non_target_owner_context(node, ctx),
9559        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9560            qualified_owner_is_known_non_target(node, ctx)
9561        }
9562        _ => false,
9563    }
9564}
9565
9566fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9567    if receiver.kind() != "identifier" {
9568        return false;
9569    }
9570    let receiver_name = node_text(receiver, ctx.source);
9571    let mut current = receiver;
9572    while let Some(parent) = ctx.ancestry.parent(current) {
9573        if parent.kind() == "compound_statement" {
9574            let mut cursor = parent.walk();
9575            for statement in parent.named_children(&mut cursor) {
9576                if statement.start_byte() >= receiver.start_byte() {
9577                    break;
9578                }
9579                if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9580                {
9581                    continue;
9582                }
9583                let type_text = statement
9584                    .child_by_field_name("type")
9585                    .or_else(|| first_type_child(statement))
9586                    .map(|type_node| node_text(type_node, ctx.source));
9587                let mut declaration_cursor = statement.walk();
9588                for child in statement.named_children(&mut declaration_cursor) {
9589                    let declarator = if child.kind() == "init_declarator" {
9590                        child.child_by_field_name("declarator")
9591                    } else if is_declarator_node(child) {
9592                        Some(child)
9593                    } else {
9594                        None
9595                    };
9596                    let Some(declarator) = declarator else {
9597                        continue;
9598                    };
9599                    if declarator.kind() == "function_declarator"
9600                        && extract_variable_name(declarator, ctx.source)
9601                            .is_some_and(|name| name == receiver_name)
9602                        && function_declarator_parameters_have_known_types(declarator, ctx)
9603                        && !constructor_style_local_declaration(
9604                            ctx.visibility,
9605                            ctx.file,
9606                            ctx.source,
9607                            declarator,
9608                            type_text,
9609                            &ctx.bindings,
9610                        )
9611                    {
9612                        return true;
9613                    }
9614                }
9615            }
9616            return false;
9617        }
9618        current = parent;
9619    }
9620    false
9621}
9622
9623fn function_declarator_parameters_have_known_types(
9624    declarator: Node<'_>,
9625    ctx: &ScanCtx<'_>,
9626) -> bool {
9627    let Some(parameters) = declarator.child_by_field_name("parameters") else {
9628        return false;
9629    };
9630    let mut cursor = parameters.walk();
9631    parameters.named_children(&mut cursor).all(|parameter| {
9632        if !matches!(
9633            parameter.kind(),
9634            "parameter_declaration" | "optional_parameter_declaration"
9635        ) {
9636            return false;
9637        }
9638        let Some(type_node) = parameter
9639            .child_by_field_name("type")
9640            .or_else(|| first_type_child(parameter))
9641        else {
9642            return false;
9643        };
9644        type_node.kind() == "primitive_type"
9645            || ctx
9646                .visibility
9647                .resolve_type_node_result(ctx.file, type_node, ctx.source)
9648                .ok()
9649                .flatten()
9650                .is_some()
9651    })
9652}
9653
9654#[derive(Clone, Copy, PartialEq, Eq)]
9655enum QualifiedOwnerResolution {
9656    Target,
9657    NonTarget,
9658    Unresolved,
9659}
9660
9661#[derive(Clone)]
9662pub enum LexicalScopeResolution {
9663    Resolved(Vec<String>),
9664    Ambiguous,
9665    Missing,
9666}
9667
9668/// One scan's memo of lexical scope reconstructions for one file, keyed by
9669/// the reconstruction's anchor range and flags, with the file's recovered
9670/// namespace regions that the key and the reconstruction both read.
9671pub struct LexicalScopeCache {
9672    resolutions: RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>,
9673    orphaned: Arc<OrphanedNamespaceScopeIndex>,
9674}
9675
9676impl LexicalScopeCache {
9677    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
9678        Self {
9679            resolutions: RefCell::new(HashMap::default()),
9680            orphaned: orphaned_namespace_scopes(visibility, file),
9681        }
9682    }
9683}
9684
9685/// The namespaces parse recovery dropped from `file`, memoized on the
9686/// analyzer (issue #1537).
9687fn orphaned_namespace_scopes(
9688    visibility: &VisibilityIndex<'_>,
9689    file: &ProjectFile,
9690) -> Arc<OrphanedNamespaceScopeIndex> {
9691    visibility
9692        .cpp()
9693        .orphaned_namespace_scopes(visibility.token(), file)
9694}
9695
9696fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9697    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
9698}
9699
9700fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9701    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
9702}
9703
9704fn is_structurally_qualified(node: Node<'_>) -> bool {
9705    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9706        && qualified_name_has_concrete_scope_separators(node)
9707}
9708
9709fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
9710    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9711        return QualifiedOwnerResolution::Unresolved;
9712    };
9713    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
9714        return QualifiedOwnerResolution::Unresolved;
9715    };
9716    // A malformed wrapper can make the parser-derived enclosing owner look
9717    // like the lexical namespace (for example tinyxml2's macro-prefixed
9718    // XMLHandle declarations). Recover the target namespace only while this
9719    // function owns scope reconstruction. An explicit recovered scope is
9720    // authoritative and enters the scoped resolver directly.
9721    if !global
9722        && !matches!(
9723            enclosing_lexical_scope_components(
9724                node,
9725                &ctx.analyzer,
9726                ctx.visibility,
9727                ctx.file,
9728                ctx.source,
9729            ),
9730            LexicalScopeResolution::Resolved(_)
9731        )
9732    {
9733        return QualifiedOwnerResolution::Unresolved;
9734    }
9735    match resolve_type_components_lexically_at_for_target_with_scope_cache(
9736        node,
9737        &components,
9738        global,
9739        &ctx.analyzer,
9740        ctx.visibility,
9741        &ctx.ordinary_type_imports,
9742        ctx.file,
9743        ctx.source,
9744        target_owner,
9745        false,
9746        Some(&ctx.lexical_scope_cache),
9747    ) {
9748        LexicalTypeResolution::Resolved { unit: owner, .. } => {
9749            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
9750                return QualifiedOwnerResolution::Target;
9751            }
9752            match cached_declaring_member_owner(&owner, ctx) {
9753                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9754                    if receiver_owner_matches_target(
9755                        &declaring_owner,
9756                        target_owner,
9757                        node.start_byte(),
9758                        ctx,
9759                    ) =>
9760                {
9761                    QualifiedOwnerResolution::Target
9762                }
9763                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9764                    if receiver_owner_is_known_non_target(
9765                        &declaring_owner,
9766                        target_owner,
9767                        node.start_byte(),
9768                        ctx,
9769                    ) =>
9770                {
9771                    QualifiedOwnerResolution::NonTarget
9772                }
9773                EnclosingMemberOwnerResolution::Owner(_)
9774                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
9775                EnclosingMemberOwnerResolution::Missing
9776                    if same_visible_symbol(&owner, target_owner) =>
9777                {
9778                    QualifiedOwnerResolution::Unresolved
9779                }
9780                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
9781            }
9782        }
9783        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
9784            QualifiedOwnerResolution::Unresolved
9785        }
9786    }
9787}
9788
9789fn qualified_callable_owner_components(
9790    node: Node<'_>,
9791    source: &str,
9792) -> Option<(Vec<String>, bool)> {
9793    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9794        || !qualified_name_has_concrete_scope_separators(node)
9795    {
9796        return None;
9797    }
9798    let owner = qualified_owner_components(node, source)?;
9799    Some((owner.names, owner.global))
9800}
9801
9802fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
9803    if !matches!(
9804        node.kind(),
9805        "identifier"
9806            | "type_identifier"
9807            | "namespace_identifier"
9808            | "qualified_identifier"
9809            | "scoped_identifier"
9810            | "scoped_type_identifier"
9811            | "template_type"
9812            | "template_function"
9813    ) {
9814        return None;
9815    }
9816    let components = cpp_type_name_components(node, source)?;
9817    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
9818}
9819
9820pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
9821    let mut namespaces = Vec::new();
9822    for parent in enclosing_cpp_namespace_nodes(node) {
9823        if let Some(name) = parent.child_by_field_name("name") {
9824            let mut components = Vec::new();
9825            if append_cpp_name_components(name, source, &mut components).is_some() {
9826                namespaces.push(components);
9827            }
9828        }
9829    }
9830    namespaces.into_iter().flatten().collect()
9831}
9832
9833pub fn enclosing_lexical_scope_components(
9834    node: Node<'_>,
9835    analyzer: &CppGraphSource<'_>,
9836    visibility: &VisibilityIndex<'_>,
9837    file: &ProjectFile,
9838    source: &str,
9839) -> LexicalScopeResolution {
9840    enclosing_lexical_scope_components_with_unresolved_owner(
9841        node,
9842        analyzer,
9843        visibility,
9844        file,
9845        source,
9846        false,
9847        false,
9848        &orphaned_namespace_scopes(visibility, file),
9849    )
9850}
9851
9852#[allow(clippy::too_many_arguments)]
9853fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
9854    node: Node<'_>,
9855    analyzer: &CppGraphSource<'_>,
9856    visibility: &VisibilityIndex<'_>,
9857    file: &ProjectFile,
9858    source: &str,
9859    allow_structured_unresolved_owner: bool,
9860    ignore_function_owner: bool,
9861    cache: Option<&LexicalScopeCache>,
9862) -> LexicalScopeResolution {
9863    let Some(cache) = cache else {
9864        return enclosing_lexical_scope_components_with_unresolved_owner(
9865            node,
9866            analyzer,
9867            visibility,
9868            file,
9869            source,
9870            allow_structured_unresolved_owner,
9871            ignore_function_owner,
9872            &orphaned_namespace_scopes(visibility, file),
9873        );
9874    };
9875    let orphaned = &cache.orphaned;
9876    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
9877    // A namespace body or translation unit anchor spans every recovered
9878    // region among its children, whose scopes differ; the region bounds keep
9879    // the key exact there and leave an anchor inside one region unchanged.
9880    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
9881        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
9882        None => (anchor_start, anchor_end),
9883    };
9884    let key = (
9885        anchor_start,
9886        anchor_end,
9887        allow_structured_unresolved_owner,
9888        ignore_function_owner,
9889    );
9890    if let Some(cached) = cache.resolutions.borrow().get(&key).cloned() {
9891        return cached;
9892    }
9893    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
9894        node,
9895        analyzer,
9896        visibility,
9897        file,
9898        source,
9899        allow_structured_unresolved_owner,
9900        ignore_function_owner,
9901        orphaned,
9902    );
9903    cache.resolutions.borrow_mut().insert(key, resolved.clone());
9904    resolved
9905}
9906
9907fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
9908    let mut current = node;
9909    loop {
9910        if matches!(
9911            current.kind(),
9912            "function_definition"
9913                | "class_specifier"
9914                | "struct_specifier"
9915                | "union_specifier"
9916                | "namespace_definition"
9917                | "translation_unit"
9918        ) {
9919            return (current.start_byte(), current.end_byte());
9920        }
9921        let Some(parent) = current.parent() else {
9922            return (current.start_byte(), current.end_byte());
9923        };
9924        current = parent;
9925    }
9926}
9927
9928#[allow(clippy::too_many_arguments)]
9929fn enclosing_lexical_scope_components_with_unresolved_owner(
9930    node: Node<'_>,
9931    analyzer: &CppGraphSource<'_>,
9932    visibility: &VisibilityIndex<'_>,
9933    file: &ProjectFile,
9934    source: &str,
9935    allow_structured_unresolved_owner: bool,
9936    ignore_function_owner: bool,
9937    orphaned: &OrphanedNamespaceScopeIndex,
9938) -> LexicalScopeResolution {
9939    #[cfg(any(test, feature = "test-support"))]
9940    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
9941    // One ancestor climb collects the namespace chain, the class chain, the
9942    // nearest function definition and both displaced-class-shape facts.
9943    // `Node::parent` re-descends from the root on every call (tree-sitter
9944    // 0.24+), so the four separate climbs this replaces each cost another
9945    // near-full-AST scan per reconstruction on a large flat file (#1927).
9946    let mut namespaces = Vec::new();
9947    let mut classes = Vec::new();
9948    let mut function_definition = None;
9949    let mut displaced_class_scope = false;
9950    let mut current = node.parent();
9951    while let Some(parent) = current {
9952        match parent.kind() {
9953            "namespace_definition" => {
9954                if let Some(name) = parent.child_by_field_name("name") {
9955                    let mut components = Vec::new();
9956                    if append_cpp_name_components(name, source, &mut components).is_some() {
9957                        namespaces.push((
9958                            parent.start_byte(),
9959                            components,
9960                            namespace_definition_has_real_braces(parent),
9961                        ));
9962                    }
9963                }
9964            }
9965            "class_specifier" | "struct_specifier" | "union_specifier" => {
9966                if let Some(name) = parent.child_by_field_name("name") {
9967                    let mut components = Vec::new();
9968                    if append_cpp_name_components(name, source, &mut components).is_some() {
9969                        classes.push(components);
9970                    }
9971                }
9972            }
9973            "function_definition" => {
9974                if function_definition.is_none() {
9975                    function_definition = Some(parent);
9976                }
9977                displaced_class_scope = displaced_class_scope
9978                    || parent.child_by_field_name("type").is_some_and(|type_node| {
9979                        matches!(
9980                            type_node.kind(),
9981                            "class_specifier" | "struct_specifier" | "union_specifier"
9982                        )
9983                    })
9984                    || is_malformed_wrapper_function_definition(parent);
9985            }
9986            _ => {}
9987        }
9988        current = parent.parent();
9989    }
9990    namespaces.reverse();
9991    // The parsed chain can stop short of the namespaces tree-sitter's error
9992    // recovery closed early; the orphaned index restores them (#1537).
9993    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
9994    let mut scope = namespace.clone();
9995    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
9996    // qualified function declarator. It is recovery scaffolding, not a real
9997    // callable owner, and must not overwrite the indexed class scope retained
9998    // by the wrapper body (#2249).
9999    let has_qualified_function_owner = function_definition
10000        .filter(|function| !is_malformed_wrapper_function_definition(*function))
10001        .and_then(function_definition_owner_lookup_node)
10002        .is_some_and(|owner| {
10003            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10004        });
10005    let indexed_scope = displaced_class_scope
10006        .then(|| {
10007            indexed_structural_class_scope(visibility, file, node, source)
10008                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10009        })
10010        .flatten()
10011        .or_else(|| {
10012            // A qualified out-of-line definition can lose its class owner from
10013            // the parser tree when a namespace sentinel or export macro wraps
10014            // the declaration.  Recover the indexed owner scope up front so
10015            // all unqualified type references in the body see the same class
10016            // boundary as C++ lookup, including aliases in parameters and
10017            // local declarations (not only template-argument leaves).
10018            (has_qualified_function_owner && function_definition.is_some())
10019                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10020                .flatten()
10021                .filter(|indexed| {
10022                    qualified_owner_scope_is_recoverable(
10023                        indexed,
10024                        &namespace,
10025                        &classes,
10026                        function_definition
10027                            .and_then(function_definition_owner_lookup_node)
10028                            .and_then(|owner| qualified_callable_owner_components(owner, source))
10029                            .map(|(components, _)| components),
10030                    )
10031                })
10032        })
10033        .or_else(|| {
10034            // A nested class declaration can likewise lose one of its outer
10035            // class ancestors from the CST.  Prefer the exact indexed
10036            // structural class scope. Its same-file declaration range and
10037            // exact template-id match already prove the owner, including a
10038            // specialization whose parser component retains only the primary
10039            // name. Keep the suffix guard for graph-only recovery, which lacks
10040            // that direct syntax-range proof.
10041            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10042                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10043                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10044                })
10045            })
10046        })
10047        .or_else(|| {
10048            // Retain the existing indexed lexical-scope recovery for
10049            // unqualified function bodies.  It is intentionally last so a
10050            // canonical class owner wins whenever one is available.
10051            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10052                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10053                .flatten()
10054                .filter(|indexed| indexed.len() > namespace.len())
10055        });
10056    if let Some(indexed_scope) = indexed_scope.as_ref() {
10057        // A macro-displaced namespace can leave the parser with the real class
10058        // body but no namespace ancestor. Prefer the structural class match;
10059        // partial specializations whose structured name cannot round-trip use
10060        // the exact indexed enclosing-owner chain instead.
10061        scope = indexed_scope.clone();
10062        classes.clear();
10063    }
10064
10065    if !ignore_function_owner
10066        && has_qualified_function_owner
10067        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10068    {
10069        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10070            return LexicalScopeResolution::Missing;
10071        };
10072        // Resolve the out-of-line owner from the parser namespace before the
10073        // provisional indexed parent can influence the answer. Per-file
10074        // extraction can assign the first same-depth using namespace to a
10075        // bare owner. The structured using resolver instead selects the
10076        // namespace whose visible class has the owner name (#1838).
10077        let imports = visibility.ordinary_type_import_cell(file);
10078        let owner_resolution = resolve_type_components_lexically_at_scoped(
10079            function,
10080            &owner,
10081            global,
10082            analyzer,
10083            visibility,
10084            &imports,
10085            file,
10086            source,
10087            None,
10088            false,
10089            false,
10090            false,
10091            namespace.clone(),
10092        );
10093        match owner_resolution {
10094            LexicalTypeResolution::Resolved {
10095                unit, components, ..
10096            } if is_indexed_class_owner(analyzer, &unit) => {
10097                scope = components;
10098                classes.clear();
10099            }
10100            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10101            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10102                match visibility
10103                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10104                {
10105                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10106                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10107                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10108                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10109                            qualified_owner_scope_is_recoverable(
10110                                indexed,
10111                                &namespace,
10112                                &classes,
10113                                Some(owner.clone()),
10114                            )
10115                        }) {
10116                            scope = indexed.clone();
10117                        } else {
10118                            scope = if global || owner.starts_with(&namespace) {
10119                                owner
10120                            } else {
10121                                let mut relative = namespace;
10122                                relative.extend(owner);
10123                                relative
10124                            };
10125                        }
10126                    }
10127                    LexicalTypeResolution::Missing => {
10128                        // Structural lexical resolution cannot see an owner class that
10129                        // is reachable only through an in-scope `using namespace`
10130                        // directive, so it would otherwise hard-fail here. The indexed
10131                        // definition already carries the true fully-qualified owner
10132                        // (its package reflects the directive), so recover the real
10133                        // enclosing scope from the analyzer graph -- exactly the scope
10134                        // chain real C++ unqualified lookup traverses. Only the strict
10135                        // callers reach this arm; the best-effort callers above keep
10136                        // their existing structural guess (and its query profile).
10137                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10138                            .or_else(|| {
10139                                indexed_namespace_qualified_scope(
10140                                    analyzer, visibility, file, node, &owner,
10141                                )
10142                            }) {
10143                            Some(indexed) => scope = indexed,
10144                            None => return LexicalScopeResolution::Missing,
10145                        }
10146                    }
10147                }
10148            }
10149        }
10150    }
10151
10152    classes.reverse();
10153    scope.extend(classes.into_iter().flatten());
10154    LexicalScopeResolution::Resolved(scope)
10155}
10156
10157fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10158    let mut current = node.parent();
10159    while let Some(parent) = current {
10160        if parent.kind() == "function_definition"
10161            && parent.child_by_field_name("type").is_some_and(|type_node| {
10162                matches!(
10163                    type_node.kind(),
10164                    "class_specifier" | "struct_specifier" | "union_specifier"
10165                )
10166            })
10167        {
10168            return true;
10169        }
10170        current = parent.parent();
10171    }
10172    false
10173}
10174
10175fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10176    let mut current = node.parent();
10177    while let Some(parent) = current {
10178        if parent.kind() == "function_definition"
10179            && is_malformed_wrapper_function_definition(parent)
10180        {
10181            return true;
10182        }
10183        current = parent.parent();
10184    }
10185    false
10186}
10187
10188/// Whether a function definition is a recovery wrapper with a non-function
10189/// declarator. The older grammar's evidence is parser damage; the
10190/// macro-declaration grammar keeps the same envelope clean, where a
10191/// `macro_modifier`/`macro_modifier_declarator`/`macro_type_specifier` head is
10192/// admitted only where the parse would otherwise contain an error.
10193pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10194    let mut cursor = node.walk();
10195    let recovery_evidence = node.has_error()
10196        || node.named_children(&mut cursor).any(|child| {
10197            matches!(
10198                child.kind(),
10199                "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10200            )
10201        });
10202    recovery_evidence
10203        && node
10204            .child_by_field_name("declarator")
10205            .is_some_and(|declarator| {
10206                declarator.kind() != "function_declarator"
10207                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10208            })
10209}
10210
10211/// Tree-sitter can make an attribute/nullability macro look like the namespace
10212/// component of a qualified function owner when it appears between the return
10213/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10214/// The recovered owner is not a C++ lexical owner, so callers resolving the
10215/// ordinary return/parameter type must retain the surrounding namespace scope.
10216fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10217    node.child_by_field_name("scope")
10218        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10219        .is_some()
10220}
10221
10222fn indexed_structural_class_scope(
10223    visibility: &VisibilityIndex<'_>,
10224    file: &ProjectFile,
10225    node: Node<'_>,
10226    source: &str,
10227) -> Option<Vec<String>> {
10228    let mut current = node.parent();
10229    while let Some(parent) = current {
10230        if matches!(
10231            parent.kind(),
10232            "class_specifier" | "struct_specifier" | "union_specifier"
10233        ) {
10234            return visibility.indexed_structural_class_scope(file, parent, source);
10235        }
10236        current = parent.parent();
10237    }
10238    None
10239}
10240
10241/// Check that an indexed owner scope is a structured completion of the parser
10242/// scope rather than an unrelated same-spelled declaration.
10243///
10244/// Error recovery around C++ namespace sentinels can preserve only a subset of
10245/// the namespace/class chain.  The indexed definition still carries the full
10246/// owner path, so require every surviving parser component to occur in order
10247/// and require any explicit qualified function owner to be the terminal
10248/// suffix.  An empty parser scope is accepted only with that qualified-owner
10249/// suffix evidence; a lone top-level short name is not evidence that a
10250/// namespace was lost.
10251fn qualified_owner_scope_is_recoverable(
10252    indexed: &[String],
10253    namespace: &[String],
10254    classes: &[Vec<String>],
10255    qualified_owner: Option<Vec<String>>,
10256) -> bool {
10257    if let Some(owner) = qualified_owner {
10258        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10259            return false;
10260        }
10261        // A malformed namespace sentinel can erase every parser namespace
10262        // ancestor.  The indexed enclosing callable still provides an
10263        // authoritative class owner, so the qualified owner suffix itself is
10264        // enough evidence in that case.  When namespace components survived,
10265        // retain the stricter subsequence check below.
10266        if namespace.is_empty() {
10267            return true;
10268        }
10269        if indexed.len() <= namespace.len() {
10270            return false;
10271        }
10272        return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10273    }
10274    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10275    if !class_components.is_empty() {
10276        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10277    }
10278    if namespace.is_empty() || indexed.len() <= namespace.len() {
10279        return false;
10280    }
10281    super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10282}
10283
10284/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10285/// counts as the true lexical owner recovered from the indexed graph.
10286fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10287    unit.is_class()
10288        && !analyzer
10289            .type_alias_provider()
10290            .is_some_and(|provider| provider.is_type_alias(unit))
10291}
10292
10293/// Recover the enclosing member's true lexical scope from the *indexed*
10294/// definition when structural resolution cannot see the owner class.
10295///
10296/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10297/// {...}`) whose owner class is reachable only through an in-scope `using
10298/// namespace X;` directive cannot be resolved by `resolve_type_components_
10299/// lexically`, which walks structural lexical tiers and never consults
10300/// using-directives. The definition itself, however, is indexed with its true
10301/// fully-qualified identity (its package already reflects the directive), so
10302/// the analyzer graph knows the real owner. Walk from the reference's indexed
10303/// enclosing code unit up to the innermost enclosing class and return that
10304/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10305/// -- exactly the scope chain C++ unqualified lookup traverses.
10306fn indexed_enclosing_owner_scope(
10307    analyzer: &CppGraphSource<'_>,
10308    visibility: &VisibilityIndex<'_>,
10309    file: &ProjectFile,
10310    node: Node<'_>,
10311) -> Option<Vec<String>> {
10312    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10313}
10314
10315/// Recover the lexical scope of an out-of-line definition whose syntactic
10316/// qualifier names a *namespace* rather than a class (`void out::target(int)
10317/// {...}` written at file scope, issue #3096).
10318///
10319/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10320/// nothing here and the strict caller would fail closed for every unqualified
10321/// reference in the body. C++ unqualified lookup inside such a definition
10322/// proceeds from the named namespace outward, exactly as inside a `namespace
10323/// out { ... }` block, and the indexed definition already carries that scope
10324/// (`["out"]` for `out::target`).
10325///
10326/// Take it only when a namespace of that name is visible from the file and the
10327/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10328/// the graph does not hold (`void MissingContainer::call()`) has no such
10329/// namespace, so it still fails closed -- and it is rejected by the in-memory
10330/// visibility index, before any indexed enclosing-unit query.
10331fn indexed_namespace_qualified_scope(
10332    analyzer: &CppGraphSource<'_>,
10333    visibility: &VisibilityIndex<'_>,
10334    file: &ProjectFile,
10335    node: Node<'_>,
10336    owner: &[String],
10337) -> Option<Vec<String>> {
10338    let name = owner.last().expect("a qualified owner has one component");
10339    if !visibility
10340        .visible_identifier_candidates(file, name)
10341        .any(|unit| unit.is_module())
10342    {
10343        return None;
10344    }
10345    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10346    indexed.ends_with(owner).then_some(indexed)
10347}
10348
10349fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10350    let start = enclosing_context(node, ctx).enclosing?;
10351    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10352        ctx.analyzer.parent_of(unit)
10353    })
10354    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10355}
10356
10357pub fn resolve_type_node_lexically(
10358    node: Node<'_>,
10359    analyzer: &CppGraphSource<'_>,
10360    visibility: &VisibilityIndex<'_>,
10361    ordinary_type_imports: &OrdinaryTypeImportCell,
10362    file: &ProjectFile,
10363    source: &str,
10364) -> LexicalTypeResolution {
10365    let Some((components, global)) = type_reference_components(node, source) else {
10366        return LexicalTypeResolution::Missing;
10367    };
10368    let resolution = resolve_type_components_lexically_at(
10369        node,
10370        &components,
10371        global,
10372        analyzer,
10373        visibility,
10374        ordinary_type_imports,
10375        file,
10376        source,
10377    );
10378    if !is_cpp_template_argument_type_leaf(node) {
10379        return resolution;
10380    }
10381
10382    // Error recovery can detach a member function from its class while
10383    // leaving an unqualified type argument (for example `error_type` in
10384    // `expected<..., error_type>`). The normal structural scope then lacks
10385    // the class owner and resolves the wrong same-spelled alias, or fails
10386    // closed. The indexed enclosing unit still carries the authoritative
10387    // class scope; retry only this narrowly-shaped leaf with that scope.
10388    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10389        return resolution;
10390    };
10391    let namespace_scope = enclosing_namespace_components(node, source);
10392    if indexed_scope.len() <= namespace_scope.len() {
10393        return resolution;
10394    }
10395    let indexed = visibility.resolve_type_components_lexically(
10396        analyzer,
10397        file,
10398        &components,
10399        global,
10400        &indexed_scope,
10401    );
10402    match indexed {
10403        LexicalTypeResolution::Resolved { ref unit, .. }
10404            if !visibility
10405                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10406        {
10407            resolution
10408        }
10409        LexicalTypeResolution::Resolved { .. } => indexed,
10410        _ => resolution,
10411    }
10412}
10413
10414#[allow(clippy::too_many_arguments)]
10415pub fn resolve_type_node_lexically_for_target(
10416    node: Node<'_>,
10417    analyzer: &CppGraphSource<'_>,
10418    visibility: &VisibilityIndex<'_>,
10419    ordinary_type_imports: &OrdinaryTypeImportCell,
10420    file: &ProjectFile,
10421    source: &str,
10422    target: &CodeUnit,
10423    scope_cache: Option<&LexicalScopeCache>,
10424    recovered_scope: Option<&[String]>,
10425) -> LexicalTypeResolution {
10426    let Some((reference_components, global)) = type_reference_components(node, source) else {
10427        return LexicalTypeResolution::Missing;
10428    };
10429    let terminal = reference_components
10430        .last()
10431        .expect("type reference components are non-empty");
10432    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
10433        return LexicalTypeResolution::Missing;
10434    }
10435    let template_arguments = cpp_template_reference_arguments(node, source);
10436    let selects_concrete_specialization =
10437        template_arguments.is_some() && visibility.is_template_specialization(target);
10438    if !selects_concrete_specialization
10439        && !visibility.structured_type_reference_may_resolve_to_target(
10440            analyzer,
10441            file,
10442            std::slice::from_ref(terminal),
10443            false,
10444            &[],
10445            target,
10446        )
10447    {
10448        return LexicalTypeResolution::Missing;
10449    }
10450    if let Some(arguments) = template_arguments.as_ref() {
10451        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
10452            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10453                node,
10454                &reference_components,
10455                global,
10456                analyzer,
10457                visibility,
10458                ordinary_type_imports,
10459                file,
10460                source,
10461                recovered_scope,
10462            )
10463        } else {
10464            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10465                node,
10466                &reference_components,
10467                global,
10468                analyzer,
10469                visibility,
10470                ordinary_type_imports,
10471                file,
10472                source,
10473                scope_cache,
10474            )
10475        };
10476        return match alias_resolution {
10477            LexicalTypeResolution::Resolved {
10478                unit,
10479                components,
10480                candidates,
10481            } if visibility.template_alias_arguments_preserve_target(
10482                analyzer, file, &unit, arguments, target,
10483            ) =>
10484            {
10485                LexicalTypeResolution::Resolved {
10486                    unit: target.clone(),
10487                    components,
10488                    candidates,
10489                }
10490            }
10491            LexicalTypeResolution::Resolved {
10492                unit,
10493                components,
10494                candidates,
10495            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
10496                Ok(resolved_unit) => {
10497                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
10498                        .then(|| {
10499                            target_guided_malformed_template_alias_resolution(
10500                                node,
10501                                analyzer,
10502                                visibility,
10503                                file,
10504                                arguments,
10505                                &reference_components,
10506                                target,
10507                            )
10508                        })
10509                        .flatten();
10510                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
10511                        unit: resolved_unit,
10512                        components,
10513                        candidates,
10514                    })
10515                }
10516                Err(_) => LexicalTypeResolution::Ambiguous,
10517            },
10518            LexicalTypeResolution::Missing => {
10519                let target_preserving = if let Some(recovered_scope) = recovered_scope {
10520                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
10521                        node,
10522                        &reference_components,
10523                        global,
10524                        analyzer,
10525                        visibility,
10526                        ordinary_type_imports,
10527                        file,
10528                        source,
10529                        target,
10530                        true,
10531                        recovered_scope,
10532                    )
10533                } else {
10534                    resolve_type_components_lexically_at_for_target_with_scope_cache(
10535                        node,
10536                        &reference_components,
10537                        global,
10538                        analyzer,
10539                        visibility,
10540                        ordinary_type_imports,
10541                        file,
10542                        source,
10543                        target,
10544                        true,
10545                        scope_cache,
10546                    )
10547                };
10548                match target_preserving {
10549                    LexicalTypeResolution::Resolved {
10550                        unit: _,
10551                        components,
10552                        candidates,
10553                    } if template_reference_candidates_select_target(
10554                        node,
10555                        &candidates,
10556                        analyzer,
10557                        visibility,
10558                        file,
10559                        source,
10560                        target,
10561                    ) =>
10562                    {
10563                        LexicalTypeResolution::Resolved {
10564                            unit: target.clone(),
10565                            components,
10566                            candidates,
10567                        }
10568                    }
10569                    _ => target_guided_malformed_template_alias_resolution(
10570                        node,
10571                        analyzer,
10572                        visibility,
10573                        file,
10574                        arguments,
10575                        &reference_components,
10576                        target,
10577                    )
10578                    .unwrap_or(LexicalTypeResolution::Missing),
10579                }
10580            }
10581            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
10582        };
10583    }
10584    let resolution = if let Some(recovered_scope) = recovered_scope {
10585        resolve_type_components_lexically_at_for_target_with_recovered_scope(
10586            node,
10587            &reference_components,
10588            global,
10589            analyzer,
10590            visibility,
10591            ordinary_type_imports,
10592            file,
10593            source,
10594            target,
10595            true,
10596            recovered_scope,
10597        )
10598    } else {
10599        resolve_type_components_lexically_at_for_target_with_scope_cache(
10600            node,
10601            &reference_components,
10602            global,
10603            analyzer,
10604            visibility,
10605            ordinary_type_imports,
10606            file,
10607            source,
10608            target,
10609            true,
10610            scope_cache,
10611        )
10612    };
10613    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
10614        target_guided_qualified_namespace_function_type_resolution(
10615            node,
10616            &reference_components,
10617            global,
10618            analyzer,
10619            visibility,
10620            ordinary_type_imports,
10621            file,
10622            source,
10623            target,
10624        )
10625        .unwrap_or(resolution)
10626    } else {
10627        resolution
10628    };
10629    if !is_cpp_template_argument_type_leaf(node) {
10630        return resolution;
10631    }
10632
10633    // Preprocessor recovery can lift a member declaration out of its class
10634    // field list.  The unqualified template argument is then resolved from
10635    // the namespace only, even though the indexed enclosing callable still
10636    // identifies the class owner.  Retry this exact leaf against that
10637    // structured owner scope; ordinary type nodes must continue to use the
10638    // parser-derived lexical scope so unrelated same-spelled aliases remain
10639    // excluded.
10640    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10641        return resolution;
10642    };
10643    let namespace_scope = enclosing_namespace_components(node, source);
10644    if indexed_scope.len() <= namespace_scope.len() {
10645        return resolution;
10646    }
10647    let indexed = visibility.resolve_type_components_lexically_for_target(
10648        analyzer,
10649        file,
10650        &reference_components,
10651        global,
10652        &indexed_scope,
10653        target,
10654    );
10655    match indexed {
10656        LexicalTypeResolution::Resolved {
10657            ref unit,
10658            ref candidates,
10659            ..
10660        } if (same_visible_symbol(unit, target)
10661            || candidates
10662                .iter()
10663                .any(|candidate| same_visible_symbol(candidate, target)))
10664            && visibility
10665                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10666        {
10667            indexed
10668        }
10669        _ => resolution,
10670    }
10671}
10672
10673/// Recover the lexical namespace of an out-of-line namespace function whose
10674/// relative qualifier is reachable through a `using namespace` directive.
10675///
10676/// Per-file declaration extraction can attach `void schema::consume(...)` to
10677/// the first visible namespace prefix when more than one using-directive is
10678/// active. A matching visible free-function declaration still proves the
10679/// complete namespace. Target-guided inverse lookup may use that namespace to
10680/// retry a parameter or body type, but only when the target lives in the same
10681/// namespace and the declaration's callable arity matches the definition.
10682#[allow(clippy::too_many_arguments)]
10683fn target_guided_qualified_namespace_function_type_resolution(
10684    node: Node<'_>,
10685    components: &[String],
10686    global: bool,
10687    analyzer: &CppGraphSource<'_>,
10688    visibility: &VisibilityIndex<'_>,
10689    ordinary_type_imports: &OrdinaryTypeImportCell,
10690    file: &ProjectFile,
10691    source: &str,
10692    target: &CodeUnit,
10693) -> Option<LexicalTypeResolution> {
10694    let function_definition = std::iter::successors(Some(node), |current| current.parent())
10695        .find(|current| current.kind() == "function_definition")?;
10696    let function = function_definition_name_node(function_definition)?;
10697    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
10698    let target_namespace = target.package_name();
10699    if target_namespace.is_empty() {
10700        return None;
10701    }
10702    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10703        brokk_bifrost_core::analyzer::Language::Cpp,
10704        target_namespace,
10705    );
10706    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
10707    {
10708        return None;
10709    }
10710
10711    let function_name = node_text(function_terminal_node(function), source);
10712    let definition_unit = canonical_callable_at_node(
10713        function_definition,
10714        analyzer,
10715        visibility,
10716        file,
10717        source,
10718        TargetKind::FreeFunction,
10719    )?;
10720    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
10721    let declaration_proves_namespace = visibility
10722        .visible_identifier_candidates(file, function_name)
10723        .filter(|candidate| {
10724            candidate.is_function()
10725                && type_owner_of(analyzer, candidate).is_none()
10726                && candidate.package_name() == target_namespace
10727        })
10728        .any(|candidate| {
10729            cpp_callable_arity(analyzer, candidate)
10730                .is_none_or(|known| known.accepts(definition_arity))
10731        });
10732    if !declaration_proves_namespace {
10733        return None;
10734    }
10735
10736    let resolution = resolve_type_components_lexically_at_scoped(
10737        node,
10738        components,
10739        global,
10740        analyzer,
10741        visibility,
10742        ordinary_type_imports,
10743        file,
10744        source,
10745        Some(target),
10746        true,
10747        false,
10748        false,
10749        target_scope,
10750    );
10751    match &resolution {
10752        LexicalTypeResolution::Resolved {
10753            unit, candidates, ..
10754        } if (same_visible_symbol(unit, target)
10755            || candidates
10756                .iter()
10757                .any(|candidate| same_visible_symbol(candidate, target)))
10758            && visibility
10759                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10760        {
10761            Some(resolution)
10762        }
10763        LexicalTypeResolution::Resolved { .. }
10764        | LexicalTypeResolution::Ambiguous
10765        | LexicalTypeResolution::Missing => None,
10766    }
10767}
10768
10769#[allow(clippy::too_many_arguments)]
10770fn target_guided_malformed_template_alias_resolution(
10771    node: Node<'_>,
10772    analyzer: &CppGraphSource<'_>,
10773    visibility: &VisibilityIndex<'_>,
10774    file: &ProjectFile,
10775    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
10776    components: &[String],
10777    target: &CodeUnit,
10778) -> Option<LexicalTypeResolution> {
10779    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
10780        return None;
10781    }
10782
10783    let identifier = &components[0];
10784    let namespace =
10785        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
10786    let namespace_name = namespace.join("::");
10787    let candidates = visibility
10788        .visible_identifier_candidates(file, identifier)
10789        .filter(|candidate| {
10790            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
10791                && visibility.type_candidate_may_be_visible_before_reference(
10792                    analyzer,
10793                    file,
10794                    candidate,
10795                    node.start_byte(),
10796                )
10797        })
10798        .cloned()
10799        .collect::<Vec<_>>();
10800    let first = candidates.first()?;
10801    if !candidates
10802        .iter()
10803        .all(|candidate| same_logical_symbol(first, candidate))
10804        || !candidates.iter().all(|candidate| {
10805            visibility.template_alias_arguments_preserve_target(
10806                analyzer, file, candidate, arguments, target,
10807            )
10808        })
10809    {
10810        return None;
10811    }
10812
10813    let mut resolved_components = namespace;
10814    resolved_components.push(identifier.clone());
10815    Some(LexicalTypeResolution::Resolved {
10816        unit: target.clone(),
10817        components: resolved_components,
10818        candidates,
10819    })
10820}
10821
10822fn resolve_type_node_lexically_for_target_without_visibility(
10823    node: Node<'_>,
10824    analyzer: &CppGraphSource<'_>,
10825    visibility: &VisibilityIndex<'_>,
10826    file: &ProjectFile,
10827    source: &str,
10828    target: &CodeUnit,
10829) -> LexicalTypeResolution {
10830    let Some((components, global)) = type_reference_components(node, source) else {
10831        return LexicalTypeResolution::Missing;
10832    };
10833    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10834        node,
10835        analyzer,
10836        visibility,
10837        file,
10838        source,
10839        true,
10840        recovered_macro_decorated_declarator_type(node)
10841            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10842        &orphaned_namespace_scopes(visibility, file),
10843    ) {
10844        LexicalScopeResolution::Resolved(scope) => scope,
10845        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10846        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10847    };
10848    visibility.resolve_type_components_lexically_for_target(
10849        analyzer,
10850        file,
10851        &components,
10852        global,
10853        &lexical_scope,
10854        target,
10855    )
10856}
10857
10858fn type_node_has_exact_target_identity_without_visibility(
10859    node: Node<'_>,
10860    analyzer: &CppGraphSource<'_>,
10861    visibility: &VisibilityIndex<'_>,
10862    file: &ProjectFile,
10863    source: &str,
10864    target: &CodeUnit,
10865) -> bool {
10866    let Some((components, global)) = type_reference_components(node, source) else {
10867        return false;
10868    };
10869    let LexicalScopeResolution::Resolved(lexical_scope) =
10870        enclosing_lexical_scope_components_with_unresolved_owner(
10871            node,
10872            analyzer,
10873            visibility,
10874            file,
10875            source,
10876            true,
10877            recovered_macro_decorated_declarator_type(node)
10878                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10879            &orphaned_namespace_scopes(visibility, file),
10880        )
10881    else {
10882        return false;
10883    };
10884    let target_name = cpp_name_for(target);
10885    lexical_component_tiers(&components, global, &lexical_scope)
10886        .any(|qualified| qualified.join("::") == target_name)
10887}
10888
10889pub fn resolve_using_enum_declaration_owner(
10890    node: Node<'_>,
10891    analyzer: &CppGraphSource<'_>,
10892    visibility: &VisibilityIndex<'_>,
10893    ordinary_type_imports: &OrdinaryTypeImportCell,
10894    file: &ProjectFile,
10895    source: &str,
10896) -> LexicalTypeResolution {
10897    let Some(type_node) = using_enum_declaration_type_node(node) else {
10898        return LexicalTypeResolution::Missing;
10899    };
10900    let mut components = Vec::new();
10901    if append_cpp_name_components(type_node, source, &mut components).is_none()
10902        || components.is_empty()
10903    {
10904        return LexicalTypeResolution::Missing;
10905    }
10906    resolve_type_components_lexically_at(
10907        type_node,
10908        &components,
10909        is_globally_qualified_cpp_name(type_node),
10910        analyzer,
10911        visibility,
10912        ordinary_type_imports,
10913        file,
10914        source,
10915    )
10916}
10917
10918pub fn resolve_ordinary_using_declaration_owner(
10919    node: Node<'_>,
10920    analyzer: &CppGraphSource<'_>,
10921    visibility: &VisibilityIndex<'_>,
10922    file: &ProjectFile,
10923    source: &str,
10924) -> LexicalTypeResolution {
10925    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
10926        return LexicalTypeResolution::Missing;
10927    };
10928    let mut components = Vec::new();
10929    if append_cpp_name_components(type_node, source, &mut components).is_none()
10930        || components.len() < 2
10931    {
10932        return LexicalTypeResolution::Missing;
10933    }
10934    let lexical_scope =
10935        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
10936            LexicalScopeResolution::Resolved(scope) => scope,
10937            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10938            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10939        };
10940    visibility.resolve_type_components_lexically(
10941        analyzer,
10942        file,
10943        &components,
10944        is_globally_qualified_cpp_name(type_node),
10945        &lexical_scope,
10946    )
10947}
10948
10949pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10950    (node.kind() == "using_declaration"
10951        && (0..node.child_count()).any(|index| {
10952            node.child(index)
10953                .is_some_and(|child| child.kind() == "enum")
10954        }))
10955    .then(|| node.named_child(0))
10956    .flatten()
10957}
10958
10959pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10960    (node.kind() == "using_declaration"
10961        && using_enum_declaration_type_node(node).is_none()
10962        && using_namespace_directive_name_node(node).is_none())
10963    .then(|| node.named_child(0))
10964    .flatten()
10965}
10966
10967/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
10968/// undefined namespace-sentinel macro as a declaration whose type is the
10969/// all-caps sentinel and whose qualified declarator starts with a pseudo
10970/// `using` scope. The real imported name remains a structured qualified
10971/// identifier under that declarator. Recover only this exact CST envelope so
10972/// ordinary macro-decorated variables are not treated as imports.
10973fn recovered_macro_using_declaration_type_node<'tree>(
10974    node: Node<'tree>,
10975    source: &str,
10976) -> Option<(Node<'tree>, bool)> {
10977    if node.kind() != "declaration" {
10978        return None;
10979    }
10980    let macro_type = node.child_by_field_name("type")?;
10981    if macro_type.kind() != "type_identifier"
10982        || !cpp_export_macro_token(node_text(macro_type, source))
10983    {
10984        return None;
10985    }
10986    let declarator = node.child_by_field_name("declarator")?;
10987    if declarator.kind() != "qualified_identifier" {
10988        return None;
10989    }
10990    let scope = declarator.child_by_field_name("scope")?;
10991    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
10992        return None;
10993    }
10994    let target = declarator.child_by_field_name("name")?;
10995    let mut components = Vec::new();
10996    append_cpp_name_components(target, source, &mut components)?;
10997    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
10998}
10999
11000fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11001    let is_directive = node.kind() == "using_directive"
11002        || (node.kind() == "using_declaration"
11003            && (0..node.child_count()).any(|index| {
11004                node.child(index)
11005                    .is_some_and(|child| child.kind() == "namespace")
11006            }));
11007    if !is_directive {
11008        return None;
11009    }
11010    node.child_by_field_name("name")
11011        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11012}
11013
11014fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11015    let mut current = node.parent();
11016    while let Some(parent) = current {
11017        if matches!(
11018            parent.kind(),
11019            "compound_statement"
11020                | "function_definition"
11021                | "lambda_expression"
11022                | "for_statement"
11023                | "while_statement"
11024                | "if_statement"
11025                | "class_specifier"
11026                | "struct_specifier"
11027                | "union_specifier"
11028        ) {
11029            return None;
11030        }
11031        current = parent.parent();
11032    }
11033    Some(enclosing_namespace_components(node, source))
11034}
11035
11036fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11037    let mut current = node.parent();
11038    while let Some(scope) = current {
11039        if matches!(
11040            scope.kind(),
11041            "compound_statement"
11042                | "declaration_list"
11043                | "field_declaration_list"
11044                | "translation_unit"
11045        ) {
11046            let mut depth = 0;
11047            let mut ancestor = scope.parent();
11048            while let Some(parent) = ancestor {
11049                depth += 1;
11050                ancestor = parent.parent();
11051            }
11052            return Some((
11053                scope.start_byte(),
11054                scope.end_byte(),
11055                depth,
11056                scope.kind() == "compound_statement",
11057            ));
11058        }
11059        current = scope.parent();
11060    }
11061    None
11062}
11063
11064/// Build the per-file structured using index for `file`.
11065///
11066/// The result is a pure function of the file's parsed content, which is what
11067/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11068/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11069/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11070pub fn build_source_using_index(
11071    cpp: &dyn CppSource,
11072    token: QueryToken<'_>,
11073    file: &ProjectFile,
11074) -> SourceUsingIndex {
11075    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11076        return SourceUsingIndex::default();
11077    };
11078    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11079}
11080
11081fn collect_source_using_index(
11082    cpp: &dyn CppSource,
11083    source_file: &ProjectFile,
11084    root: Node<'_>,
11085    source: &str,
11086) -> SourceUsingIndex {
11087    #[cfg(not(any(test, feature = "test-support")))]
11088    let _ = cpp;
11089    let mut index = SourceUsingIndex::default();
11090    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11091    let mut stack = vec![root];
11092    while let Some(node) = stack.pop() {
11093        let target = match node.kind() {
11094            "using_directive" | "using_declaration" => {
11095                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11096                    let mut namespace_components = Vec::new();
11097                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11098                        .map(|_| EffectiveUsingTarget::Namespace {
11099                            namespace_components,
11100                            global: is_globally_qualified_cpp_name(namespace_node),
11101                        })
11102                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11103                    let mut target_components = Vec::new();
11104                    (append_cpp_name_components(type_node, source, &mut target_components)
11105                        .is_some()
11106                        && target_components.len() >= 2)
11107                        .then(|| EffectiveUsingTarget::Ordinary {
11108                            name: target_components
11109                                .last()
11110                                .expect("ordinary using has a terminal component")
11111                                .clone(),
11112                            target_components,
11113                            global: is_globally_qualified_cpp_name(type_node),
11114                        })
11115                } else {
11116                    None
11117                }
11118            }
11119            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11120                |(type_node, global)| {
11121                    let mut target_components = Vec::new();
11122                    (append_cpp_name_components(type_node, source, &mut target_components)
11123                        .is_some()
11124                        && target_components.len() >= 2)
11125                        .then(|| EffectiveUsingTarget::Ordinary {
11126                            name: target_components
11127                                .last()
11128                                .expect("recovered ordinary using has a terminal component")
11129                                .clone(),
11130                            target_components,
11131                            global,
11132                        })
11133                },
11134            ),
11135            _ => None,
11136        };
11137        if let Some(target) = target {
11138            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11139            // searches from the root. The project index visits every AST node, but only these
11140            // structured using declarations need a guard environment. Keep the cheap target
11141            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11142            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11143            #[cfg(any(test, feature = "test-support"))]
11144            cpp.record_using_guard_context_inspection_for_test();
11145            let required_guards = if callable_preprocessor_context_is_visible(node, source) {
11146                Some(HashSet::default())
11147            } else {
11148                preprocessor_guard_environment(node, source)
11149            };
11150            let Some(required_guards) = required_guards else {
11151                let mut cursor = node.walk();
11152                stack.extend(node.children(&mut cursor));
11153                continue;
11154            };
11155            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11156                ordinary_using_scope(node)
11157            {
11158                let declaration_namespace = enclosing_namespace_components(node, source);
11159                let declaration_namespace = if declaration_namespace.is_empty() {
11160                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11161                        .unwrap_or(declaration_namespace)
11162                } else {
11163                    declaration_namespace
11164                };
11165                let namespace_scope = using_named_scope(node, source);
11166                let lexical_depth = declaration_namespace.len();
11167                let binding = OrdinaryTypeImport {
11168                    target,
11169                    source: source_file.clone(),
11170                    declaration_byte: node.end_byte(),
11171                    scope_start,
11172                    scope_end,
11173                    scope_depth,
11174                    block_scope,
11175                    lexical_depth,
11176                    declaration_namespace,
11177                    namespace_scope,
11178                    resolved_target_components: None,
11179                    required_guards,
11180                };
11181                match &binding.target {
11182                    EffectiveUsingTarget::Ordinary { name, .. } => index
11183                        .ordinary_by_name
11184                        .entry(name.clone())
11185                        .or_default()
11186                        .push(binding),
11187                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11188                }
11189            }
11190        }
11191        let mut cursor = node.walk();
11192        stack.extend(node.children(&mut cursor));
11193    }
11194    index
11195}
11196
11197struct OrphanedNamespaceEnvelope {
11198    body_end: usize,
11199    components: Vec<String>,
11200    class_names: HashSet<String>,
11201}
11202
11203/// Tree-sitter can terminate a namespace body at an object-like namespace
11204/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11205/// out-of-line definitions at translation-unit scope. A block-scoped using
11206/// declaration in one of those definitions still belongs to the namespace
11207/// selected by the malformed namespace envelope. Keep the envelope scan
11208/// source-local and reuse its structural ownership evidence for each using.
11209fn collect_orphaned_namespace_envelopes(
11210    root: Node<'_>,
11211    source: &str,
11212) -> Vec<OrphanedNamespaceEnvelope> {
11213    let mut envelopes = Vec::new();
11214    let mut stack = vec![root];
11215    while let Some(current) = stack.pop() {
11216        if current.kind() == "namespace_definition"
11217            && let Some(body) = current.child_by_field_name("body")
11218            && current.end_byte() == body.end_byte()
11219            && let Some(name) = current.child_by_field_name("name")
11220        {
11221            let mut components = enclosing_namespace_components(current, source);
11222            if append_cpp_name_components(name, source, &mut components).is_some()
11223                && !components.is_empty()
11224            {
11225                let mut class_names = HashSet::default();
11226                let mut body_stack = vec![body];
11227                while let Some(node) = body_stack.pop() {
11228                    if let Some(name) = orphaned_class_definition_name(node, source) {
11229                        class_names.insert(name);
11230                    }
11231                    let mut cursor = node.walk();
11232                    if node.kind() == "ERROR" {
11233                        body_stack.extend(node.children(&mut cursor));
11234                    } else {
11235                        body_stack.extend(node.named_children(&mut cursor));
11236                    }
11237                }
11238                envelopes.push(OrphanedNamespaceEnvelope {
11239                    body_end: body.end_byte(),
11240                    components,
11241                    class_names,
11242                });
11243            }
11244        }
11245        let mut cursor = current.walk();
11246        stack.extend(current.named_children(&mut cursor));
11247    }
11248    envelopes
11249}
11250
11251fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11252    if matches!(
11253        node.kind(),
11254        "class_specifier" | "struct_specifier" | "union_specifier"
11255    ) {
11256        let body = node.child_by_field_name("body")?;
11257        let name = node.child_by_field_name("name")?;
11258        return (!name.is_missing() && !body.is_missing())
11259            .then(|| node_text(name, source).to_string());
11260    }
11261    if node.kind() != "ERROR" {
11262        return None;
11263    }
11264
11265    // When an object-like namespace macro is parsed as a function definition,
11266    // tree-sitter can place the entire class declaration inside an ERROR node
11267    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11268    // fallback structural: accept only a named class-like keyword followed by
11269    // a real body, never an arbitrary identifier mentioned in the envelope.
11270    for index in 0..node.child_count() {
11271        let Some(keyword) = node.child(index) else {
11272            continue;
11273        };
11274        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11275            continue;
11276        }
11277        let mut name = None;
11278        for next_index in (index + 1)..node.child_count() {
11279            let Some(next) = node.child(next_index) else {
11280                continue;
11281            };
11282            if next.kind() == ";" {
11283                break;
11284            }
11285            if next.kind() == "{" {
11286                return name
11287                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11288                    .map(|name_node| node_text(name_node, source).to_string());
11289            }
11290            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11291                name = Some(next);
11292            }
11293        }
11294    }
11295    None
11296}
11297
11298fn recovered_orphaned_namespace_components(
11299    node: Node<'_>,
11300    source: &str,
11301    envelopes: &[OrphanedNamespaceEnvelope],
11302) -> Option<Vec<String>> {
11303    let owner_name = orphaned_using_owner_name(node, source)?;
11304    envelopes
11305        .iter()
11306        .filter(|envelope| {
11307            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11308        })
11309        .max_by_key(|envelope| envelope.body_end)
11310        .map(|envelope| envelope.components.clone())
11311}
11312
11313fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11314    let function = std::iter::successors(node.parent(), |current| current.parent())
11315        .find(|current| current.kind() == "function_definition")?;
11316    let owner = function_definition_owner_lookup_node(function)?;
11317    let scope = owner.child_by_field_name("scope")?;
11318    let mut components = Vec::new();
11319    append_cpp_name_components(scope, source, &mut components)?;
11320    // A qualified out-of-line member definition already carries its namespace
11321    // in the declarator scope (for example `foo::Widget::run`). The recovery
11322    // path is only for parser-orphaned top-level members whose owner scope
11323    // collapsed to the bare class name; requiring that shape prevents an
11324    // earlier, unrelated namespace/class from leaking into a global function's
11325    // using-directive lookup.
11326    if components.len() != 1 {
11327        return None;
11328    }
11329    components.pop()
11330}
11331
11332fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11333    let started = Instant::now();
11334    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11335    let source_files = visibility.all_visible_source_files();
11336    if report_stats {
11337        eprintln!(
11338            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11339            source_files.len()
11340        );
11341    }
11342    let mut project = ProjectUsingIndex::default();
11343    let mut ordinary_bindings = 0usize;
11344    for source_file in &source_files {
11345        // The per-file index is memoized on the analyzer, so assembling the
11346        // project index for a fresh `VisibilityIndex` copies bindings instead
11347        // of re-walking each file's AST (#1927).
11348        let source_index = visibility
11349            .cpp()
11350            .source_using_index(visibility.token(), source_file);
11351        for (name, bindings) in &source_index.ordinary_by_name {
11352            ordinary_bindings += bindings.len();
11353            project
11354                .ordinary_by_name
11355                .entry(name.clone())
11356                .or_default()
11357                .extend(bindings.iter().cloned());
11358        }
11359        project
11360            .directives
11361            .extend(source_index.directives.iter().cloned());
11362    }
11363    if report_stats {
11364        eprintln!(
11365            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11366            source_files.len(),
11367            project.ordinary_by_name.len(),
11368            ordinary_bindings,
11369            project.directives.len(),
11370            started.elapsed().as_millis(),
11371        );
11372    }
11373    project
11374}
11375
11376fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11377    visibility.project_using_index(|| build_project_using_index(visibility))
11378}
11379
11380/// Build the immutable project-wide using index before a parallel file scan.
11381///
11382/// Keeping the `OnceLock` publication here avoids making every scanner carry
11383/// an eager index, while callers that are about to fan out can prevent one
11384/// worker from doing the whole build as its peers wait on the lock.
11385pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11386    let _ = project_using_index(visibility);
11387}
11388
11389fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11390    let (components, global) = match &binding.target {
11391        EffectiveUsingTarget::Ordinary {
11392            target_components,
11393            global,
11394            ..
11395        } => (target_components, *global),
11396        EffectiveUsingTarget::Namespace {
11397            namespace_components,
11398            global,
11399        } => (namespace_components, *global),
11400    };
11401    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11402}
11403
11404fn using_binding_target_components_for_name(
11405    binding: &OrdinaryTypeImport,
11406    project: &ProjectUsingIndex,
11407    visibility: &VisibilityIndex<'_>,
11408    file: &ProjectFile,
11409    name: &str,
11410) -> Option<Vec<String>> {
11411    // Built once per call rather than per candidate: the filter runs over every
11412    // visible identifier of `name`, and the source is the same object each time.
11413    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
11414    let visible_candidates = visibility
11415        .visible_identifier_candidates(file, name)
11416        .filter(|candidate| {
11417            candidate.is_class()
11418                || declared_type_alias(&cpp_source, candidate)
11419                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
11420        })
11421        .collect::<Vec<_>>();
11422    if visible_candidates.is_empty() {
11423        return None;
11424    }
11425    match &binding.target {
11426        EffectiveUsingTarget::Ordinary {
11427            name: imported_name,
11428            ..
11429        } if imported_name == name => {
11430            effective_using_target_tiers(binding)
11431                .into_iter()
11432                .find(|qualified| {
11433                    let qualified_name = qualified.join("::");
11434                    visible_candidates
11435                        .iter()
11436                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
11437                })
11438        }
11439        EffectiveUsingTarget::Namespace { .. } => {
11440            visibility.note_using_namespace_lookup_for_test();
11441            let target_tiers = effective_using_target_tiers(binding);
11442            let resolved = target_tiers
11443                .iter()
11444                .find(|namespace_components| {
11445                    let namespace = namespace_components.join("::");
11446                    visible_candidates.iter().any(|candidate| {
11447                        visibility.note_using_name_candidate_inspection_for_test();
11448                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
11449                            candidate_namespace == namespace
11450                                || candidate_namespace.starts_with(&format!("{namespace}::"))
11451                        })
11452                    }) || project.directives.iter().any(|candidate| {
11453                        candidate.namespace_scope.as_deref()
11454                            == Some(namespace_components.as_slice())
11455                    }) || project
11456                        .ordinary_by_name
11457                        .values()
11458                        .flatten()
11459                        .any(|candidate| {
11460                            candidate.namespace_scope.as_deref()
11461                                == Some(namespace_components.as_slice())
11462                        })
11463                })
11464                .cloned();
11465            resolved.or_else(|| {
11466                // A sole lexical namespace tier is itself enough to retain the
11467                // directive. Candidate identity is resolved later, where
11468                // target guidance and macro-expanded owner names are available.
11469                // Dropping it here makes an unrelated same-terminal type hide
11470                // the actual namespace member before lookup can compare owners.
11471                (target_tiers.len() == 1)
11472                    .then(|| target_tiers.into_iter().next())
11473                    .flatten()
11474            })
11475        }
11476        EffectiveUsingTarget::Ordinary { .. } => None,
11477    }
11478}
11479
11480fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
11481    let start = activation.checked_sub(1)?;
11482    let mut node = root.descendant_for_byte_range(start, activation)?;
11483    while node.kind() != "preproc_include" {
11484        node = node.parent()?;
11485    }
11486    Some(node)
11487}
11488
11489fn project_using_bindings(
11490    binding: OrdinaryTypeImport,
11491    visibility: &VisibilityIndex<'_>,
11492    file: &ProjectFile,
11493    root: Node<'_>,
11494    source: &str,
11495) -> Vec<OrdinaryTypeImport> {
11496    if binding.source == *file {
11497        return vec![binding];
11498    }
11499    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
11500        return Vec::new();
11501    }
11502    visibility.note_using_donor_activation_for_test();
11503    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
11504        return Vec::new();
11505    };
11506    let projections = visibility
11507        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
11508        .map_or_else(
11509            || {
11510                visibility.conditional_include_projections_for_source(
11511                    file,
11512                    prepared.as_ref(),
11513                    &binding.source,
11514                )
11515            },
11516            |activation_byte| {
11517                Arc::from([ConditionalIncludeProjection {
11518                    activation_byte,
11519                    required_guards: HashSet::default(),
11520                    partial_guards: HashSet::default(),
11521                }])
11522            },
11523        );
11524    projections
11525        .iter()
11526        .cloned()
11527        .filter_map(|projection| {
11528            let required_guards =
11529                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
11530            let mut projected = binding.clone();
11531            projected.required_guards = required_guards;
11532            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
11533        })
11534        .collect()
11535}
11536
11537fn project_using_binding_at_activation(
11538    mut binding: OrdinaryTypeImport,
11539    activation: usize,
11540    root: Node<'_>,
11541    source: &str,
11542) -> Option<OrdinaryTypeImport> {
11543    let include = include_node_for_activation(root, activation)?;
11544    let include_namespace = enclosing_namespace_components(include, source);
11545    let mut declaration_namespace = include_namespace.clone();
11546    declaration_namespace.extend(binding.declaration_namespace);
11547    binding.declaration_namespace = declaration_namespace;
11548    binding.declaration_byte = activation;
11549    if let Some(prefix) = using_named_scope(include, source) {
11550        let mut projected = prefix;
11551        projected.extend(binding.namespace_scope.take().unwrap_or_default());
11552        binding.scope_depth = projected.len();
11553        binding.block_scope = false;
11554        binding.lexical_depth = projected.len();
11555        binding.namespace_scope = Some(projected);
11556        binding.scope_start = 0;
11557        binding.scope_end = usize::MAX;
11558        Some(binding)
11559    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
11560        binding.namespace_scope = None;
11561        binding.scope_start = start;
11562        binding.scope_end = end;
11563        binding.scope_depth = depth;
11564        binding.block_scope = block_scope;
11565        binding.lexical_depth = include_namespace.len();
11566        Some(binding)
11567    } else {
11568        None
11569    }
11570}
11571
11572/// `node` may be any node of `file`'s tree; the projection reaches the tree
11573/// root itself when it needs one. `Node::parent` re-descends from the root on
11574/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
11575/// call site cost a near-full-AST scan per reference on a large flat file
11576/// (#1927); a name with no candidate bindings never pays for it.
11577pub fn effective_using_bindings_for_name(
11578    visibility: &VisibilityIndex<'_>,
11579    imports: &OrdinaryTypeImportCell,
11580    file: &ProjectFile,
11581    node: Node<'_>,
11582    source: &str,
11583    name: &str,
11584) -> Arc<[OrdinaryTypeImport]> {
11585    imports
11586        .projection_cell(name)
11587        .get_or_init(|| {
11588            let project = project_using_index(visibility);
11589            let name_bindings = project.ordinary_by_name.get(name);
11590            if name_bindings.is_none() && project.directives.is_empty() {
11591                return Arc::from(Vec::new());
11592            }
11593            let root = root_node(node);
11594            let mut projected = Vec::new();
11595            for binding in name_bindings
11596                .into_iter()
11597                .flatten()
11598                .chain(project.directives.iter())
11599            {
11600                if !visibility.source_is_visible(file, &binding.source) {
11601                    continue;
11602                }
11603                let target_components = using_binding_target_components_for_name(
11604                    binding, project, visibility, file, name,
11605                )
11606                .or_else(|| match &binding.target {
11607                    EffectiveUsingTarget::Ordinary {
11608                        name: imported_name,
11609                        target_components,
11610                        ..
11611                    } if imported_name == name => Some(target_components.clone()),
11612                    EffectiveUsingTarget::Ordinary { .. }
11613                    | EffectiveUsingTarget::Namespace { .. } => None,
11614                });
11615                let Some(target_components) = target_components else {
11616                    continue;
11617                };
11618                let mut binding = binding.clone();
11619                binding.resolved_target_components = Some(target_components);
11620                projected.extend(project_using_bindings(
11621                    binding, visibility, file, root, source,
11622                ));
11623            }
11624            Arc::from(projected)
11625        })
11626        .clone()
11627}
11628
11629pub fn initialized_ordinary_type_imports(
11630    root: Node<'_>,
11631    analyzer: &CppGraphSource<'_>,
11632    visibility: &VisibilityIndex<'_>,
11633    file: &ProjectFile,
11634    source: &str,
11635) -> OrdinaryTypeImportCell {
11636    let cell = visibility.ordinary_type_import_cell(file);
11637    let _ = (root, analyzer, source);
11638    cell
11639}
11640
11641/// `reference_guards` is the reference node's guard environment, computed once
11642/// by the caller and shared across every binding: recomputing it per binding
11643/// repeated a full ancestor climb whose every `Node::parent` step re-descends
11644/// from the root (#1927).
11645fn effective_using_binding_active(
11646    binding: &OrdinaryTypeImport,
11647    node: Node<'_>,
11648    lexical_scope: &[String],
11649    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11650    visibility: &VisibilityIndex<'_>,
11651    file: &ProjectFile,
11652) -> bool {
11653    effective_using_binding_guards_active(
11654        binding,
11655        node.start_byte(),
11656        reference_guards,
11657        visibility,
11658        file,
11659    ) && binding.namespace_scope.as_ref().map_or_else(
11660        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11661        |namespace| lexical_scope.starts_with(namespace),
11662    )
11663}
11664
11665fn effective_using_binding_guards_active(
11666    binding: &OrdinaryTypeImport,
11667    reference_byte: usize,
11668    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11669    visibility: &VisibilityIndex<'_>,
11670    file: &ProjectFile,
11671) -> bool {
11672    binding.declaration_byte <= reference_byte
11673        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
11674        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11675}
11676
11677fn effective_using_binding_guards_compatible(
11678    binding: &OrdinaryTypeImport,
11679    reference_byte: usize,
11680    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11681    visibility: &VisibilityIndex<'_>,
11682    file: &ProjectFile,
11683) -> bool {
11684    binding.source != *file
11685        && !binding.required_guards.is_empty()
11686        && binding.declaration_byte <= reference_byte
11687        && reference_guards.is_some_and(|active| {
11688            !binding.required_guards.is_subset(active)
11689                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
11690        })
11691        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11692}
11693
11694fn effective_using_binding_guards_stable_between(
11695    binding: &OrdinaryTypeImport,
11696    reference_byte: usize,
11697    visibility: &VisibilityIndex<'_>,
11698    file: &ProjectFile,
11699) -> bool {
11700    visibility.preprocessor_guards_stable_between(
11701        file,
11702        binding.declaration_byte,
11703        reference_byte,
11704        &binding.required_guards,
11705    )
11706}
11707
11708#[allow(clippy::too_many_arguments)]
11709fn binding_type_candidates(
11710    binding: &OrdinaryTypeImport,
11711    active_bindings: &[&OrdinaryTypeImport],
11712    analyzer: &CppGraphSource<'_>,
11713    visibility: &VisibilityIndex<'_>,
11714    file: &ProjectFile,
11715    name: &str,
11716    direct_target: Option<&CodeUnit>,
11717    reference_byte: usize,
11718) -> Vec<(CodeUnit, Vec<String>)> {
11719    ordinary_binding_lookup_targets(binding, active_bindings, name)
11720        .into_iter()
11721        .flat_map(|target| {
11722            let mut candidates = visibility
11723                .visible_identifier_candidates(file, name)
11724                .filter(|candidate| {
11725                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
11726                        && type_candidate_matches_lookup_components(
11727                            analyzer,
11728                            visibility,
11729                            file,
11730                            candidate,
11731                            reference_byte,
11732                            &target,
11733                        )
11734                })
11735                .cloned()
11736                .collect::<Vec<_>>();
11737            if candidates.is_empty()
11738                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11739                && let Some(target_unit) = direct_target
11740            {
11741                let expanded_target_name = macro_expanded_cpp_name_components(
11742                    visibility,
11743                    file,
11744                    target_unit,
11745                    reference_byte,
11746                );
11747                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
11748                    && expanded_target_name == target
11749                    && visibility.external_type_candidate_visible_at(
11750                        file,
11751                        target_unit,
11752                        reference_byte,
11753                    )
11754                {
11755                    candidates.push(target_unit.clone());
11756                }
11757            }
11758            if candidates.is_empty()
11759                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11760                && let Some(target_unit) = direct_target
11761            {
11762                let visible_types = visibility
11763                    .visible_identifier_candidates(file, name)
11764                    .filter(|candidate| {
11765                        candidate.is_class() || declared_type_alias(analyzer, candidate)
11766                    })
11767                    .collect::<Vec<_>>();
11768                let uniquely_names_target = !visible_types.is_empty()
11769                    && visible_types
11770                        .iter()
11771                        .all(|candidate| same_visible_symbol(candidate, target_unit));
11772                if uniquely_names_target {
11773                    candidates.extend(visible_types.into_iter().cloned());
11774                }
11775            }
11776            candidates
11777                .into_iter()
11778                .map(move |candidate| (candidate, target.clone()))
11779        })
11780        .collect()
11781}
11782
11783fn macro_expanded_cpp_name_components(
11784    visibility: &VisibilityIndex<'_>,
11785    file: &ProjectFile,
11786    unit: &CodeUnit,
11787    reference_byte: usize,
11788) -> Vec<String> {
11789    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11790        brokk_bifrost_core::analyzer::Language::Cpp,
11791        &cpp_name_for(unit),
11792    )
11793    .into_iter()
11794    .flat_map(|component| {
11795        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11796    })
11797    .collect()
11798}
11799
11800fn macro_expanded_cpp_name_component(
11801    visibility: &VisibilityIndex<'_>,
11802    file: &ProjectFile,
11803    component: String,
11804    reference_byte: usize,
11805) -> Vec<String> {
11806    let Some(replacement) =
11807        visibility.object_macro_replacement_at(file, &component, reference_byte)
11808    else {
11809        return vec![component];
11810    };
11811    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11812        brokk_bifrost_core::analyzer::Language::Cpp,
11813        &replacement,
11814    );
11815    if expanded.is_empty() {
11816        vec![component]
11817    } else {
11818        expanded
11819    }
11820}
11821
11822/// Whether a visible type has the requested qualified spelling.
11823///
11824/// Members of an inline namespace are also members of its enclosing namespace,
11825/// so an ordinary using-declaration may legally omit the inline component. The
11826/// stored FQ name retains that component to keep declarations distinct. Recover
11827/// the omitted spellings from the candidate declaration's namespace ancestors,
11828/// using CST `inline` tokens rather than guessing from names.
11829fn type_candidate_matches_lookup_components(
11830    analyzer: &CppGraphSource<'_>,
11831    visibility: &VisibilityIndex<'_>,
11832    file: &ProjectFile,
11833    candidate: &CodeUnit,
11834    reference_byte: usize,
11835    target: &[String],
11836) -> bool {
11837    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11838    if expanded == target {
11839        return true;
11840    }
11841    let Some(cpp) = analyzer.cpp else {
11842        return false;
11843    };
11844    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11845        return false;
11846    };
11847    let root = prepared.tree().root_node();
11848    for range in analyzer.ranges(candidate) {
11849        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11850        else {
11851            continue;
11852        };
11853        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11854        loop {
11855            if current.kind() == "namespace_definition"
11856                && let Some(name) = current.child_by_field_name("name")
11857            {
11858                let mut components = Vec::new();
11859                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11860                    && !components.is_empty()
11861                {
11862                    let inline = (0..current.child_count())
11863                        .filter_map(|index| current.child(index))
11864                        .any(|child| !child.is_named() && child.kind() == "inline");
11865                    namespaces.push((components, inline));
11866                }
11867            }
11868            let Some(parent) = current.parent() else {
11869                break;
11870            };
11871            current = parent;
11872        }
11873        namespaces.reverse();
11874        let mut namespace_components = Vec::new();
11875        let mut inline_indexes = HashSet::default();
11876        for (components, inline) in namespaces {
11877            for component in components {
11878                let expanded_component =
11879                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11880                if inline {
11881                    inline_indexes.extend(
11882                        namespace_components.len()
11883                            ..namespace_components.len() + expanded_component.len(),
11884                    );
11885                }
11886                namespace_components.extend(expanded_component);
11887            }
11888        }
11889        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11890            continue;
11891        }
11892        // Each inline namespace component can be present or elided. Compare
11893        // those alternatives as a small dynamic program instead of generating
11894        // every subset of a deeply nested inline-namespace chain.
11895        let mut reachable = vec![false; target.len() + 1];
11896        reachable[0] = true;
11897        for (index, component) in expanded.iter().enumerate() {
11898            let mut next = vec![false; target.len() + 1];
11899            for (target_index, reached) in reachable.iter().copied().enumerate() {
11900                if !reached {
11901                    continue;
11902                }
11903                if inline_indexes.contains(&index) {
11904                    next[target_index] = true;
11905                }
11906                if target
11907                    .get(target_index)
11908                    .is_some_and(|target_component| target_component == component)
11909                {
11910                    next[target_index + 1] = true;
11911                }
11912            }
11913            reachable = next;
11914        }
11915        if reachable[target.len()] {
11916            return true;
11917        }
11918    }
11919    false
11920}
11921
11922#[allow(clippy::too_many_arguments)]
11923fn resolved_type_import(
11924    candidates: Vec<(CodeUnit, Vec<String>)>,
11925    lexical_depth: usize,
11926    is_direct: bool,
11927    analyzer: &CppGraphSource<'_>,
11928    visibility: &VisibilityIndex<'_>,
11929    file: &ProjectFile,
11930    direct_target: Option<&CodeUnit>,
11931) -> OrdinaryTypeImportResolution {
11932    let logical = deduplicate_type_import_candidates(candidates);
11933    let selected = match logical.as_slice() {
11934        [] => return OrdinaryTypeImportResolution::Missing,
11935        [only] => only,
11936        // Several declarations of one FQN in one file are configuration
11937        // spellings of one entity, not competing types (#1845): the imported
11938        // name is unambiguous, only the branch that supplies it depends on the
11939        // build.
11940        several => {
11941            let units = several
11942                .iter()
11943                .map(|(unit, _)| unit)
11944                .collect::<Vec<&CodeUnit>>();
11945            let Some(spelling) = direct_target.and_then(|target| {
11946                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
11947            }) else {
11948                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
11949            };
11950            several
11951                .iter()
11952                .find(|(unit, _)| same_symbol(unit, spelling))
11953                .expect("the selected spelling is one of the imported candidates")
11954        }
11955    };
11956    OrdinaryTypeImportResolution::Resolved {
11957        target: selected.0.clone(),
11958        target_components: selected.1.clone(),
11959        lexical_depth,
11960        is_direct,
11961    }
11962}
11963
11964#[allow(clippy::too_many_arguments)]
11965fn ordinary_type_import_resolution(
11966    node: Node<'_>,
11967    components: &[String],
11968    global: bool,
11969    analyzer: &CppGraphSource<'_>,
11970    visibility: &VisibilityIndex<'_>,
11971    imports: &OrdinaryTypeImportCell,
11972    file: &ProjectFile,
11973    source: &str,
11974    lexical_scope: &[String],
11975    direct_target: Option<&CodeUnit>,
11976) -> OrdinaryTypeImportResolution {
11977    if global || components.len() != 1 {
11978        return OrdinaryTypeImportResolution::Missing;
11979    }
11980    let name = &components[0];
11981    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11982    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
11983    // step re-descends from the root (#1927). A name with no bindings needs
11984    // none of it, and one environment serves every binding of the reference.
11985    if bindings.is_empty() {
11986        return OrdinaryTypeImportResolution::Missing;
11987    }
11988    let reference_guards = preprocessor_guard_environment(node, source);
11989    let active = bindings
11990        .iter()
11991        .filter(|binding| {
11992            effective_using_binding_active(
11993                binding,
11994                node,
11995                lexical_scope,
11996                reference_guards.as_ref(),
11997                visibility,
11998                file,
11999            )
12000        })
12001        .collect::<Vec<_>>();
12002    let transitive = bindings
12003        .iter()
12004        .filter(|binding| {
12005            effective_using_binding_guards_active(
12006                binding,
12007                node.start_byte(),
12008                reference_guards.as_ref(),
12009                visibility,
12010                file,
12011            ) && (binding.namespace_scope.is_some()
12012                || (binding.scope_start <= node.start_byte()
12013                    && node.end_byte() <= binding.scope_end))
12014        })
12015        .collect::<Vec<_>>();
12016    ordinary_type_import_resolution_for_bindings(
12017        node,
12018        name,
12019        analyzer,
12020        visibility,
12021        file,
12022        lexical_scope,
12023        direct_target,
12024        &active,
12025        &transitive,
12026    )
12027}
12028
12029#[allow(clippy::too_many_arguments)]
12030fn compatible_foreign_type_import_resolution(
12031    node: Node<'_>,
12032    components: &[String],
12033    global: bool,
12034    analyzer: &CppGraphSource<'_>,
12035    visibility: &VisibilityIndex<'_>,
12036    imports: &OrdinaryTypeImportCell,
12037    file: &ProjectFile,
12038    source: &str,
12039    lexical_scope: &[String],
12040    direct_target: Option<&CodeUnit>,
12041) -> OrdinaryTypeImportResolution {
12042    if global || components.len() != 1 {
12043        return OrdinaryTypeImportResolution::Missing;
12044    }
12045    let name = &components[0];
12046    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12047    if bindings.is_empty() {
12048        return OrdinaryTypeImportResolution::Missing;
12049    }
12050    let reference_guards = preprocessor_guard_environment(node, source);
12051    let compatible = bindings
12052        .iter()
12053        .filter(|binding| {
12054            effective_using_binding_guards_compatible(
12055                binding,
12056                node.start_byte(),
12057                reference_guards.as_ref(),
12058                visibility,
12059                file,
12060            ) && binding.namespace_scope.as_ref().map_or_else(
12061                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12062                |namespace| lexical_scope.starts_with(namespace),
12063            )
12064        })
12065        .collect::<Vec<_>>();
12066    let transitive = bindings
12067        .iter()
12068        .filter(|binding| {
12069            effective_using_binding_guards_compatible(
12070                binding,
12071                node.start_byte(),
12072                reference_guards.as_ref(),
12073                visibility,
12074                file,
12075            ) && (binding.namespace_scope.is_some()
12076                || (binding.scope_start <= node.start_byte()
12077                    && node.end_byte() <= binding.scope_end))
12078        })
12079        .collect::<Vec<_>>();
12080    ordinary_type_import_resolution_for_bindings(
12081        node,
12082        name,
12083        analyzer,
12084        visibility,
12085        file,
12086        lexical_scope,
12087        direct_target,
12088        &compatible,
12089        &transitive,
12090    )
12091}
12092
12093#[allow(clippy::too_many_arguments)]
12094fn ordinary_type_import_resolution_for_bindings(
12095    node: Node<'_>,
12096    name: &str,
12097    analyzer: &CppGraphSource<'_>,
12098    visibility: &VisibilityIndex<'_>,
12099    file: &ProjectFile,
12100    lexical_scope: &[String],
12101    direct_target: Option<&CodeUnit>,
12102    active: &[&OrdinaryTypeImport],
12103    transitive: &[&OrdinaryTypeImport],
12104) -> OrdinaryTypeImportResolution {
12105    let mut concrete_depths = active
12106        .iter()
12107        .filter(|binding| binding.namespace_scope.is_none())
12108        .map(|binding| binding.scope_depth)
12109        .collect::<Vec<_>>();
12110    concrete_depths.sort_unstable();
12111    concrete_depths.dedup();
12112    for depth in concrete_depths.into_iter().rev() {
12113        let at_tier = active
12114            .iter()
12115            .copied()
12116            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12117        let direct = at_tier
12118            .clone()
12119            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12120            .flat_map(|binding| {
12121                binding_type_candidates(
12122                    binding,
12123                    transitive,
12124                    analyzer,
12125                    visibility,
12126                    file,
12127                    name,
12128                    direct_target,
12129                    node.start_byte(),
12130                )
12131            })
12132            .collect::<Vec<_>>();
12133        if !direct.is_empty() {
12134            return resolved_type_import(
12135                direct,
12136                lexical_scope.len(),
12137                true,
12138                analyzer,
12139                visibility,
12140                file,
12141                direct_target,
12142            );
12143        }
12144        let directives = at_tier
12145            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12146            .flat_map(|binding| {
12147                binding_type_candidates(
12148                    binding,
12149                    transitive,
12150                    analyzer,
12151                    visibility,
12152                    file,
12153                    name,
12154                    direct_target,
12155                    node.start_byte(),
12156                )
12157            })
12158            .collect::<Vec<_>>();
12159        if !directives.is_empty() {
12160            return resolved_type_import(
12161                directives,
12162                lexical_scope.len(),
12163                false,
12164                analyzer,
12165                visibility,
12166                file,
12167                direct_target,
12168            );
12169        }
12170    }
12171    for prefix_len in (0..=lexical_scope.len()).rev() {
12172        let tier = &lexical_scope[..prefix_len];
12173        let at_tier = active
12174            .iter()
12175            .copied()
12176            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12177        let direct = at_tier
12178            .clone()
12179            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12180            .flat_map(|binding| {
12181                binding_type_candidates(
12182                    binding,
12183                    transitive,
12184                    analyzer,
12185                    visibility,
12186                    file,
12187                    name,
12188                    direct_target,
12189                    node.start_byte(),
12190                )
12191            })
12192            .collect::<Vec<_>>();
12193        if !direct.is_empty() {
12194            return resolved_type_import(
12195                direct,
12196                prefix_len,
12197                true,
12198                analyzer,
12199                visibility,
12200                file,
12201                direct_target,
12202            );
12203        }
12204        let directives = at_tier
12205            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12206            .flat_map(|binding| {
12207                binding_type_candidates(
12208                    binding,
12209                    transitive,
12210                    analyzer,
12211                    visibility,
12212                    file,
12213                    name,
12214                    direct_target,
12215                    node.start_byte(),
12216                )
12217            })
12218            .collect::<Vec<_>>();
12219        if !directives.is_empty() {
12220            return resolved_type_import(
12221                directives,
12222                prefix_len,
12223                false,
12224                analyzer,
12225                visibility,
12226                file,
12227                direct_target,
12228            );
12229        }
12230    }
12231    OrdinaryTypeImportResolution::Missing
12232}
12233
12234#[allow(clippy::too_many_arguments)]
12235pub fn resolve_type_components_lexically_at(
12236    node: Node<'_>,
12237    components: &[String],
12238    global: bool,
12239    analyzer: &CppGraphSource<'_>,
12240    visibility: &VisibilityIndex<'_>,
12241    ordinary_type_imports: &OrdinaryTypeImportCell,
12242    file: &ProjectFile,
12243    source: &str,
12244) -> LexicalTypeResolution {
12245    resolve_type_components_lexically_at_inner(
12246        node,
12247        components,
12248        global,
12249        analyzer,
12250        visibility,
12251        ordinary_type_imports,
12252        file,
12253        source,
12254        None,
12255        false,
12256        false,
12257        false,
12258        None,
12259    )
12260}
12261
12262/// Resolve a type at its lexical reference site while retaining the identity
12263/// of an alias that C++ lookup selects.
12264///
12265/// Forward navigation uses the selected spelling as its destination, whereas
12266/// graph attribution normally canonicalizes an alias to its target. Both
12267/// surfaces must still apply the same ordinary using-declarations, declaration
12268/// order, guard state, and lexical-depth precedence.
12269#[allow(clippy::too_many_arguments)]
12270pub fn resolve_type_components_lexically_at_preserving_alias(
12271    node: Node<'_>,
12272    components: &[String],
12273    global: bool,
12274    analyzer: &CppGraphSource<'_>,
12275    visibility: &VisibilityIndex<'_>,
12276    file: &ProjectFile,
12277    source: &str,
12278) -> LexicalTypeResolution {
12279    let ordinary_type_imports =
12280        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12281    resolve_type_components_lexically_at_inner(
12282        node,
12283        components,
12284        global,
12285        analyzer,
12286        visibility,
12287        &ordinary_type_imports,
12288        file,
12289        source,
12290        None,
12291        false,
12292        true,
12293        true,
12294        None,
12295    )
12296}
12297
12298#[allow(clippy::too_many_arguments)]
12299fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12300    node: Node<'_>,
12301    components: &[String],
12302    global: bool,
12303    analyzer: &CppGraphSource<'_>,
12304    visibility: &VisibilityIndex<'_>,
12305    ordinary_type_imports: &OrdinaryTypeImportCell,
12306    file: &ProjectFile,
12307    source: &str,
12308    scope_cache: Option<&LexicalScopeCache>,
12309) -> LexicalTypeResolution {
12310    resolve_type_components_lexically_at_inner(
12311        node,
12312        components,
12313        global,
12314        analyzer,
12315        visibility,
12316        ordinary_type_imports,
12317        file,
12318        source,
12319        None,
12320        false,
12321        true,
12322        false,
12323        scope_cache,
12324    )
12325}
12326
12327#[allow(clippy::too_many_arguments)]
12328fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12329    node: Node<'_>,
12330    components: &[String],
12331    global: bool,
12332    analyzer: &CppGraphSource<'_>,
12333    visibility: &VisibilityIndex<'_>,
12334    ordinary_type_imports: &OrdinaryTypeImportCell,
12335    file: &ProjectFile,
12336    source: &str,
12337    target: &CodeUnit,
12338    apply_structured_prefilter: bool,
12339    scope_cache: Option<&LexicalScopeCache>,
12340) -> LexicalTypeResolution {
12341    resolve_type_components_lexically_at_inner(
12342        node,
12343        components,
12344        global,
12345        analyzer,
12346        visibility,
12347        ordinary_type_imports,
12348        file,
12349        source,
12350        Some(target),
12351        apply_structured_prefilter,
12352        false,
12353        false,
12354        scope_cache,
12355    )
12356}
12357
12358#[allow(clippy::too_many_arguments)]
12359fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12360    node: Node<'_>,
12361    components: &[String],
12362    global: bool,
12363    analyzer: &CppGraphSource<'_>,
12364    visibility: &VisibilityIndex<'_>,
12365    ordinary_type_imports: &OrdinaryTypeImportCell,
12366    file: &ProjectFile,
12367    source: &str,
12368    recovered_scope: &[String],
12369) -> LexicalTypeResolution {
12370    resolve_type_components_in_authoritative_scope(
12371        node,
12372        components,
12373        global,
12374        analyzer,
12375        visibility,
12376        ordinary_type_imports,
12377        file,
12378        source,
12379        None,
12380        false,
12381        true,
12382        false,
12383        recovered_scope.to_vec(),
12384    )
12385}
12386
12387#[allow(clippy::too_many_arguments)]
12388fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12389    node: Node<'_>,
12390    components: &[String],
12391    global: bool,
12392    analyzer: &CppGraphSource<'_>,
12393    visibility: &VisibilityIndex<'_>,
12394    ordinary_type_imports: &OrdinaryTypeImportCell,
12395    file: &ProjectFile,
12396    source: &str,
12397    target: &CodeUnit,
12398    apply_structured_prefilter: bool,
12399    recovered_scope: &[String],
12400) -> LexicalTypeResolution {
12401    resolve_type_components_in_authoritative_scope(
12402        node,
12403        components,
12404        global,
12405        analyzer,
12406        visibility,
12407        ordinary_type_imports,
12408        file,
12409        source,
12410        Some(target),
12411        apply_structured_prefilter,
12412        false,
12413        false,
12414        recovered_scope.to_vec(),
12415    )
12416}
12417
12418#[allow(clippy::too_many_arguments)]
12419fn resolve_type_components_lexically_at_inner(
12420    node: Node<'_>,
12421    components: &[String],
12422    global: bool,
12423    analyzer: &CppGraphSource<'_>,
12424    visibility: &VisibilityIndex<'_>,
12425    ordinary_type_imports: &OrdinaryTypeImportCell,
12426    file: &ProjectFile,
12427    source: &str,
12428    direct_target: Option<&CodeUnit>,
12429    apply_structured_prefilter: bool,
12430    preserve_alias: bool,
12431    allow_compatible_foreign_import: bool,
12432    scope_cache: Option<&LexicalScopeCache>,
12433) -> LexicalTypeResolution {
12434    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12435    let lexical_scope_started = Instant::now();
12436    if report_stats {
12437        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
12438    }
12439    let lexical_scope = if global {
12440        Vec::new()
12441    } else {
12442        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
12443            node,
12444            analyzer,
12445            visibility,
12446            file,
12447            source,
12448            true,
12449            recovered_macro_decorated_declarator_type(node)
12450                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
12451            scope_cache,
12452        ) {
12453            LexicalScopeResolution::Resolved(scope) => scope,
12454            LexicalScopeResolution::Ambiguous => {
12455                if report_stats {
12456                    eprintln!(
12457                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
12458                        lexical_scope_started.elapsed().as_millis(),
12459                    );
12460                }
12461                return LexicalTypeResolution::Ambiguous;
12462            }
12463            LexicalScopeResolution::Missing => {
12464                if report_stats {
12465                    eprintln!(
12466                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
12467                        lexical_scope_started.elapsed().as_millis(),
12468                    );
12469                }
12470                return LexicalTypeResolution::Missing;
12471            }
12472        }
12473    };
12474    if report_stats {
12475        eprintln!(
12476            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
12477            lexical_scope.len(),
12478            lexical_scope_started.elapsed().as_millis(),
12479        );
12480    }
12481    resolve_type_components_lexically_at_scoped(
12482        node,
12483        components,
12484        global,
12485        analyzer,
12486        visibility,
12487        ordinary_type_imports,
12488        file,
12489        source,
12490        direct_target,
12491        apply_structured_prefilter,
12492        preserve_alias,
12493        allow_compatible_foreign_import,
12494        lexical_scope,
12495    )
12496}
12497
12498#[allow(clippy::too_many_arguments)]
12499fn resolve_type_components_lexically_at_scoped(
12500    node: Node<'_>,
12501    components: &[String],
12502    global: bool,
12503    analyzer: &CppGraphSource<'_>,
12504    visibility: &VisibilityIndex<'_>,
12505    ordinary_type_imports: &OrdinaryTypeImportCell,
12506    file: &ProjectFile,
12507    source: &str,
12508    direct_target: Option<&CodeUnit>,
12509    apply_structured_prefilter: bool,
12510    preserve_alias: bool,
12511    allow_compatible_foreign_import: bool,
12512    mut lexical_scope: Vec<String>,
12513) -> LexicalTypeResolution {
12514    if !global
12515        && components.len() == 1
12516        // The two constant-time conditions run before the ancestor climb: each
12517        // `Node::parent` step re-descends from the root (#1927).
12518        && let Some(target) = direct_target
12519        && lexical_scope
12520            .last()
12521            .is_none_or(|last| last != &components[0])
12522        // A recovered class may contain a real member function nested inside
12523        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
12524        // XMLConstHandle). The nearest function_definition is then the member
12525        // itself, so inspect the complete ancestor chain.
12526        && has_malformed_wrapper_function_definition_ancestor(node)
12527        && let Some(indexed_namespace) =
12528            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
12529        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
12530    {
12531        lexical_scope = indexed_namespace;
12532    }
12533    resolve_type_components_in_authoritative_scope(
12534        node,
12535        components,
12536        global,
12537        analyzer,
12538        visibility,
12539        ordinary_type_imports,
12540        file,
12541        source,
12542        direct_target,
12543        apply_structured_prefilter,
12544        preserve_alias,
12545        allow_compatible_foreign_import,
12546        lexical_scope,
12547    )
12548}
12549
12550/// Resolve within a scope already proven by recovered syntax.
12551///
12552/// Unlike parser-derived scope, this scope must not be replaced with the
12553/// queried target's namespace: doing so would let target guidance override a
12554/// nearer declaration represented by the recovered syntax.
12555#[allow(clippy::too_many_arguments)]
12556fn resolve_type_components_in_authoritative_scope(
12557    node: Node<'_>,
12558    components: &[String],
12559    global: bool,
12560    analyzer: &CppGraphSource<'_>,
12561    visibility: &VisibilityIndex<'_>,
12562    ordinary_type_imports: &OrdinaryTypeImportCell,
12563    file: &ProjectFile,
12564    source: &str,
12565    direct_target: Option<&CodeUnit>,
12566    apply_structured_prefilter: bool,
12567    preserve_alias: bool,
12568    allow_compatible_foreign_import: bool,
12569    lexical_scope: Vec<String>,
12570) -> LexicalTypeResolution {
12571    if apply_structured_prefilter
12572        && direct_target.is_some()
12573        && !preserve_alias
12574        && !global
12575        && components.len() == 1
12576        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
12577    {
12578        return LexicalTypeResolution::Missing;
12579    }
12580    // A recovered macro-prefixed return type can share its global spelling
12581    // with aliases from mutually exclusive included headers. C++ lookup uses
12582    // the declaration physically present earlier in this file; the visibility
12583    // index deliberately retains every configuration alternative. Restore
12584    // that precedence only for the exact macro-decorated head (recovered by
12585    // the older grammar, or its clean macro-declaration reading) and lexical
12586    // tier.
12587    if !global
12588        && components.len() == 1
12589        && (recovered_macro_decorated_type_node(node).is_some()
12590            || macro_modifier_decorated_type_node(node))
12591        && let Some(resolution) = recovered_same_file_type_alias_resolution(
12592            node,
12593            components,
12594            analyzer,
12595            visibility,
12596            file,
12597            direct_target,
12598            &lexical_scope,
12599        )
12600    {
12601        return resolution;
12602    }
12603    if apply_structured_prefilter
12604        && let Some(target) = direct_target
12605        && !preserve_alias
12606        && !visibility.structured_type_reference_may_resolve_to_target(
12607            analyzer,
12608            file,
12609            components,
12610            global,
12611            &lexical_scope,
12612            target,
12613        )
12614    {
12615        return LexicalTypeResolution::Missing;
12616    }
12617    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12618    let lexical_normal_started = Instant::now();
12619    if report_stats {
12620        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
12621    }
12622    let normal = if preserve_alias {
12623        visibility.resolve_type_components_lexically_for_forward(
12624            analyzer,
12625            file,
12626            components,
12627            global,
12628            &lexical_scope,
12629        )
12630    } else {
12631        direct_target.map_or_else(
12632            || {
12633                visibility.resolve_type_components_lexically(
12634                    analyzer,
12635                    file,
12636                    components,
12637                    global,
12638                    &lexical_scope,
12639                )
12640            },
12641            |target| {
12642                visibility.resolve_type_components_lexically_for_target(
12643                    analyzer,
12644                    file,
12645                    components,
12646                    global,
12647                    &lexical_scope,
12648                    target,
12649                )
12650            },
12651        )
12652    };
12653    if report_stats {
12654        eprintln!(
12655            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
12656            lexical_normal_started.elapsed().as_millis(),
12657        );
12658    }
12659    let normal = match normal {
12660        LexicalTypeResolution::Resolved { ref unit, .. }
12661            if !visibility
12662                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
12663                && !direct_target.is_some_and(|target| {
12664                    let candidate_refs = visibility
12665                        .visible_identifier_candidates(file, target.identifier())
12666                        .collect::<Vec<_>>();
12667                    visibility.c_tag_declaration_family_matches_target(
12668                        analyzer,
12669                        file,
12670                        &candidate_refs,
12671                        target,
12672                    )
12673                }) =>
12674        {
12675            LexicalTypeResolution::Missing
12676        }
12677        resolution => resolution,
12678    };
12679    let normal_depth = match &normal {
12680        LexicalTypeResolution::Resolved { components, .. } => {
12681            Some(components.len().saturating_sub(1))
12682        }
12683        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
12684    };
12685    // Ordinary using-declarations participate in unqualified lookup at their
12686    // lexical scope. They therefore replace the resolver's terminal/global
12687    // fallback at the same or a shallower depth. A declaration in a more deeply
12688    // nested named scope is the closer lexical result and remains authoritative.
12689    // Ambiguous imports fail closed unless such a closer declaration exists.
12690    let ordinary_import_started = Instant::now();
12691    if report_stats {
12692        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
12693    }
12694    let ordinary_import_resolution = ordinary_type_import_resolution(
12695        node,
12696        components,
12697        global,
12698        analyzer,
12699        visibility,
12700        ordinary_type_imports,
12701        file,
12702        source,
12703        &lexical_scope,
12704        direct_target,
12705    );
12706    if report_stats {
12707        eprintln!(
12708            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
12709            ordinary_import_started.elapsed().as_millis(),
12710        );
12711    }
12712    let resolution = match ordinary_import_resolution {
12713        OrdinaryTypeImportResolution::Missing => normal,
12714        OrdinaryTypeImportResolution::Resolved {
12715            lexical_depth,
12716            is_direct,
12717            ..
12718        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
12719            || normal_depth.is_some_and(|depth| {
12720                depth > lexical_depth || (!is_direct && depth == lexical_depth)
12721            }) =>
12722        {
12723            normal
12724        }
12725        OrdinaryTypeImportResolution::Resolved {
12726            target,
12727            target_components,
12728            ..
12729        } => visibility.resolve_imported_type_candidate(
12730            analyzer,
12731            file,
12732            &target,
12733            &target_components,
12734            direct_target,
12735            preserve_alias,
12736        ),
12737        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
12738            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
12739        {
12740            normal
12741        }
12742        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12743    };
12744    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12745        return resolution;
12746    }
12747
12748    // A foreign header can contribute an exact using binding from a build
12749    // configuration that is compatible with, but not implied by, the current
12750    // reference. Forward navigation has no "unproven" result channel, so it
12751    // may still navigate through one unique structured import after ordinary
12752    // lookup misses. Inverse attribution keeps using its target-guided path
12753    // and records the same evidence as unproven (#940).
12754    match compatible_foreign_type_import_resolution(
12755        node,
12756        components,
12757        global,
12758        analyzer,
12759        visibility,
12760        ordinary_type_imports,
12761        file,
12762        source,
12763        &lexical_scope,
12764        None,
12765    ) {
12766        OrdinaryTypeImportResolution::Missing => resolution,
12767        OrdinaryTypeImportResolution::Resolved {
12768            target,
12769            target_components,
12770            ..
12771        } => visibility.resolve_imported_type_candidate(
12772            analyzer,
12773            file,
12774            &target,
12775            &target_components,
12776            None,
12777            true,
12778        ),
12779        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12780    }
12781}
12782
12783fn recovered_same_file_type_alias_resolution(
12784    node: Node<'_>,
12785    components: &[String],
12786    analyzer: &CppGraphSource<'_>,
12787    visibility: &VisibilityIndex<'_>,
12788    file: &ProjectFile,
12789    direct_target: Option<&CodeUnit>,
12790    lexical_scope: &[String],
12791) -> Option<LexicalTypeResolution> {
12792    debug_assert_eq!(components.len(), 1);
12793    debug_assert!(
12794        recovered_macro_decorated_type_node(node).is_some()
12795            || macro_modifier_decorated_type_node(node)
12796    );
12797    let alias_provider = analyzer.type_alias_provider()?;
12798    for qualified in lexical_component_tiers(components, false, lexical_scope) {
12799        let candidates = visibility
12800            .visible_identifier_candidates(file, &components[0])
12801            .filter(|candidate| {
12802                candidate.source() == file
12803                    && canonical_cpp_scope_components(candidate) == qualified
12804                    && visibility
12805                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12806            })
12807            .collect::<Vec<_>>();
12808        if candidates.is_empty() {
12809            continue;
12810        }
12811        if candidates
12812            .iter()
12813            .any(|candidate| !alias_provider.is_type_alias(candidate))
12814        {
12815            return None;
12816        }
12817        let unit = if let Some(target) = direct_target {
12818            visibility.unique_type_candidate_preserving_target(
12819                analyzer,
12820                file,
12821                &candidates,
12822                target,
12823            )?
12824        } else {
12825            let first = candidates[0];
12826            if candidates
12827                .iter()
12828                .any(|candidate| !same_visible_symbol(candidate, first))
12829            {
12830                return None;
12831            }
12832            first.clone()
12833        };
12834        return Some(LexicalTypeResolution::Resolved {
12835            unit,
12836            components: qualified,
12837            candidates: candidates.into_iter().cloned().collect(),
12838        });
12839    }
12840    None
12841}
12842
12843fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12844    matches!(
12845        structured_owner_context_resolution(node, ctx),
12846        StructuredOwnerContextResolution::SelfTarget
12847            | StructuredOwnerContextResolution::InheritedTarget
12848    )
12849}
12850
12851/// A bare/`this->` member call whose name resolves, through the enclosing class's base
12852/// hierarchy or through a lexically enclosing class, to the target member declared on
12853/// another owner. This is a genuine external usage of that owner's member rather than a
12854/// same-type self call.
12855fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12856    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
12857        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
12858    }) else {
12859        return matches!(
12860            structured_owner_context_resolution(node, ctx),
12861            StructuredOwnerContextResolution::InheritedTarget
12862        );
12863    };
12864    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12865        return false;
12866    };
12867    let chain = structured_enclosing_owner_chain(node, ctx);
12868    let Some(innermost) = chain.first() else {
12869        return false;
12870    };
12871    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12872        return false;
12873    }
12874    let Some(arity) = ctx
12875        .visibility
12876        .call_arity_evidence(ctx.file, call, ctx.source)
12877        .exact()
12878    else {
12879        return false;
12880    };
12881    for enclosing_owner in &chain {
12882        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12883            return true;
12884        }
12885        match resolve_declaring_callable_owner(
12886            &ctx.analyzer,
12887            ctx.visibility,
12888            ctx.file,
12889            cached_declaring_member_owner(enclosing_owner, ctx),
12890            &ctx.spec.member_name,
12891            arity,
12892        ) {
12893            EnclosingMemberOwnerResolution::Owner(owner) => {
12894                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12895            }
12896            EnclosingMemberOwnerResolution::Ambiguous => return false,
12897            // The name is not declared in this class or its bases, so C++
12898            // unqualified lookup continues in the next enclosing class (#3095).
12899            EnclosingMemberOwnerResolution::Missing => {}
12900        }
12901    }
12902    false
12903}
12904
12905fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12906    matches!(
12907        structured_owner_context_resolution(node, ctx),
12908        StructuredOwnerContextResolution::NonTarget
12909    )
12910}
12911
12912fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12913    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12914        return false;
12915    };
12916    let mut current = ctx.ancestry.parent(node);
12917    while let Some(parent) = current {
12918        if parent.kind() == "function_definition" {
12919            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
12920                return false;
12921            };
12922            if let Some(owners) = out_of_line_member_definition_owner(
12923                &ctx.analyzer,
12924                ctx.visibility,
12925                ctx.file,
12926                ctx.source,
12927                owner_lookup,
12928            ) && let Some((_, owner)) = owners.innermost()
12929            {
12930                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
12931            }
12932            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
12933                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12934            }
12935            return false;
12936        }
12937        current = ctx.ancestry.parent(parent);
12938    }
12939    false
12940}
12941
12942#[derive(Clone, Copy)]
12943enum StructuredOwnerContextResolution {
12944    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
12945    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
12946    SelfTarget,
12947    /// The enclosing class does not declare the member; lookup reaches the target owner
12948    /// through a base of that class, or through a lexically enclosing class (a nested
12949    /// class calling the outer class's member). Either way the reference is a genuine
12950    /// external usage OF that owner's member (e.g. `Derived` calling inherited
12951    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
12952    InheritedTarget,
12953    NonTarget,
12954    Ambiguous,
12955    Missing,
12956}
12957
12958fn structured_owner_context_resolution(
12959    node: Node<'_>,
12960    ctx: &ScanCtx<'_>,
12961) -> StructuredOwnerContextResolution {
12962    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12963        return StructuredOwnerContextResolution::Missing;
12964    };
12965    let chain = structured_enclosing_owner_chain(node, ctx);
12966    let Some(innermost) = chain.first() else {
12967        return StructuredOwnerContextResolution::Missing;
12968    };
12969    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12970        return StructuredOwnerContextResolution::SelfTarget;
12971    }
12972    // Unqualified lookup searches the enclosing class and its bases, then each lexically
12973    // enclosing class outward, and stops at the first scope that declares the name (#3095).
12974    // Only the immediately enclosing class makes a reference a same-type self call; any
12975    // other owner it reaches is a genuine usage of that owner's member.
12976    for enclosing_owner in &chain {
12977        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12978            return StructuredOwnerContextResolution::InheritedTarget;
12979        }
12980        match cached_declaring_member_owner(enclosing_owner, ctx) {
12981            EnclosingMemberOwnerResolution::Owner(owner)
12982                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
12983            {
12984                return StructuredOwnerContextResolution::InheritedTarget;
12985            }
12986            EnclosingMemberOwnerResolution::Owner(_) => {
12987                return StructuredOwnerContextResolution::NonTarget;
12988            }
12989            EnclosingMemberOwnerResolution::Ambiguous => {
12990                return StructuredOwnerContextResolution::Ambiguous;
12991            }
12992            EnclosingMemberOwnerResolution::Missing => {}
12993        }
12994    }
12995    StructuredOwnerContextResolution::Missing
12996}
12997
12998fn cached_declaring_member_owner(
12999    receiver_owner: &CodeUnit,
13000    ctx: &ScanCtx<'_>,
13001) -> EnclosingMemberOwnerResolution {
13002    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
13003        return cached;
13004    }
13005    let resolved = resolve_declaring_member_owner(
13006        &ctx.analyzer,
13007        ctx.visibility,
13008        ctx.file,
13009        receiver_owner,
13010        &ctx.spec.member_name,
13011    );
13012    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13013        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13014    } else {
13015        resolved
13016    };
13017    ctx.member_owner_cache
13018        .borrow_mut()
13019        .insert(receiver_owner.clone(), resolved.clone());
13020    resolved
13021}
13022
13023/// Recover a member's declaring owner when parser recovery omitted its
13024/// in-class declaration but retained an out-of-line definition. Ordinary
13025/// visible-member lookup runs first. The structured definition index then
13026/// supplies the missing member fact at each hierarchy level, so an indexed
13027/// derived override still hides the queried base member and distinct base
13028/// paths still fail closed.
13029fn indexed_declaring_owner_for_recovered_member(
13030    receiver_owner: &CodeUnit,
13031    ctx: &ScanCtx<'_>,
13032) -> EnclosingMemberOwnerResolution {
13033    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13034        return EnclosingMemberOwnerResolution::Missing;
13035    };
13036    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13037        return EnclosingMemberOwnerResolution::Missing;
13038    }
13039    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13040        return EnclosingMemberOwnerResolution::Missing;
13041    };
13042    let Some(receiver_owner) =
13043        ctx.visibility
13044            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13045    else {
13046        return EnclosingMemberOwnerResolution::Ambiguous;
13047    };
13048    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13049        ctx.visibility
13050            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13051    }) else {
13052        return EnclosingMemberOwnerResolution::Missing;
13053    };
13054
13055    let owner_declares_member = |owner: &CodeUnit| {
13056        if same_visible_symbol(owner, &target_owner) {
13057            return true;
13058        }
13059        let mut member_fq = owner.fq().clone();
13060        member_fq.push(
13061            ctx.spec
13062                .target
13063                .fq()
13064                .last()
13065                .expect("a method target has a terminal member segment"),
13066        );
13067        ctx.analyzer
13068            .definitions(&member_fq.display(segment_interner()))
13069            .any(|child| child.is_function())
13070    };
13071    if owner_declares_member(&receiver_owner) {
13072        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13073    }
13074
13075    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
13076    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13077    let mut declaring_owner = None;
13078    while let Some(raw_owner) = stack.pop() {
13079        let Some(owner) =
13080            ctx.visibility
13081                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13082        else {
13083            return EnclosingMemberOwnerResolution::Ambiguous;
13084        };
13085        let propagated = propagated_counts.entry(owner.clone()).or_default();
13086        if *propagated == 2 {
13087            continue;
13088        }
13089        *propagated += 1;
13090        if owner_declares_member(&owner) {
13091            if declaring_owner.is_some() {
13092                return EnclosingMemberOwnerResolution::Ambiguous;
13093            }
13094            declaring_owner = Some(owner);
13095            continue;
13096        }
13097        stack.extend(hierarchy.get_direct_ancestors(&owner));
13098    }
13099    declaring_owner
13100        .map(EnclosingMemberOwnerResolution::Owner)
13101        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13102}
13103
13104/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13105/// lookup searches each in turn before it reaches the enclosing namespaces.
13106fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13107    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13108        return Vec::new();
13109    };
13110    // The innermost owner is whatever enclosing-owner resolution produced, so
13111    // it is kept as given; only the walk outward is filtered to classes.
13112    brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13113        ctx.analyzer.parent_of(unit)
13114    })
13115    .collect()
13116}
13117
13118fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13119    // Declaration recovery can index the true class/member ranges even when
13120    // the original error tree wraps that region in a bogus function. Prefer
13121    // the analyzer's exact enclosing-owner graph at the reference byte before
13122    // interpreting such a wrapper as a real callable owner.
13123    if (has_recovered_class_shape_ancestor(node)
13124        || has_malformed_wrapper_function_definition_ancestor(node))
13125        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13126    {
13127        return Some(owner);
13128    }
13129    let mut current = ctx.ancestry.parent(node);
13130    while let Some(parent) = current {
13131        if parent.kind() == "function_definition" {
13132            let owner_lookup = function_definition_owner_lookup_node(parent);
13133            if let Some(owner_lookup) = owner_lookup
13134                && let Some(owners) = out_of_line_member_definition_owner(
13135                    &ctx.analyzer,
13136                    ctx.visibility,
13137                    ctx.file,
13138                    ctx.source,
13139                    owner_lookup,
13140                )
13141                && let Some((_, owner)) = owners.innermost()
13142            {
13143                return Some(owner.clone());
13144            }
13145            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13146                return Some(owner);
13147            }
13148            if let Some(owner) = enclosing_context(parent, ctx)
13149                .owner
13150                .filter(|owner| owner.is_class())
13151            {
13152                return Some(owner);
13153            }
13154            if let Some(owner_lookup) = owner_lookup
13155                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13156            {
13157                return Some(owner);
13158            }
13159            break;
13160        }
13161        current = ctx.ancestry.parent(parent);
13162    }
13163    enclosing_context(node, ctx)
13164        .owner
13165        .filter(|owner| owner.is_class())
13166}
13167
13168fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13169    let target_owner = ctx.spec.owner.as_ref()?;
13170    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13171    let owner_name = owner_components.last()?;
13172    let mut candidates = Vec::new();
13173    for candidate in ctx
13174        .visibility
13175        .visible_identifier_candidates(ctx.file, owner_name)
13176        .filter(|candidate| candidate.is_class())
13177    {
13178        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13179            brokk_bifrost_core::analyzer::Language::Cpp,
13180            &cpp_name_for(candidate),
13181        );
13182        if !components.ends_with(&owner_components)
13183            || candidates
13184                .iter()
13185                .any(|existing| same_logical_symbol(existing, candidate))
13186        {
13187            continue;
13188        }
13189        candidates.push(candidate.clone());
13190    }
13191    let [candidate] = candidates.as_slice() else {
13192        return None;
13193    };
13194    (same_logical_symbol(candidate, target_owner)
13195        && target_group_contains_owner_peer(candidate, ctx))
13196    .then(|| candidate.clone())
13197}