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, is_recovered_exported_class_base_type_node, node_text,
8    normalize_cpp_whitespace, recovered_macro_return_type_node,
9};
10use crate::graph::CppGraphSource;
11use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
12use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
13use crate::graph::hits::{
14    enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
15    push_declared_reference_hit, push_definition_hit, push_hit, push_recovered_definition_hit,
16    push_recursive_reference_hit, push_reference_hit_range, push_self_receiver_hit, push_type_hit,
17    push_type_hit_range, push_unproven_definition_hit, push_unproven_hit,
18    push_unproven_reference_hit_range,
19};
20use crate::graph::resolver::*;
21use crate::graph::syntax::{
22    function_macro_replacement_span, object_macro_replacement_type_references,
23    qualified_callable_value,
24};
25use crate::graph_support::CppSource;
26use brokk_bifrost_core::analyzer::fq_name::segment_interner;
27use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
28use brokk_bifrost_core::analyzer::query_token::QueryToken;
29use brokk_bifrost_core::analyzer::tree_walk::{
30    ParentIndex, WalkControl, children_iter, push_named_children_reversed, walk_named_tree_preorder,
31};
32use brokk_bifrost_core::analyzer::usages::common::same_node;
33use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
34use brokk_bifrost_core::analyzer::usages::local_inference::{
35    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
36};
37use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
38use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
39use brokk_bifrost_core::hash::{HashMap, HashSet};
40#[cfg(any(test, feature = "test-support"))]
41use std::cell::Cell;
42use std::cell::RefCell;
43use std::collections::BTreeSet;
44use std::sync::Arc;
45use std::time::Instant;
46use tree_sitter::Node;
47
48#[cfg(any(test, feature = "test-support"))]
49thread_local! {
50    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
51    static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
52}
53
54#[cfg(any(test, feature = "test-support"))]
55pub fn reset_type_reference_candidate_scan_count_for_test() {
56    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
57}
58
59#[cfg(any(test, feature = "test-support"))]
60pub fn type_reference_candidate_scan_count_for_test() -> usize {
61    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
62}
63
64pub struct ScanState<'a> {
65    pub max_usages: usize,
66    pub hits: &'a mut BTreeSet<UsageHit>,
67    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
68    pub raw_match_count: &'a mut usize,
69    pub limit_exceeded: &'a mut bool,
70}
71
72pub struct ScanCtx<'a> {
73    pub analyzer: CppGraphSource<'a>,
74    pub visibility: &'a VisibilityIndex<'a>,
75    pub file: &'a ProjectFile,
76    pub source: &'a str,
77    /// The parent of every node in the file being scanned, recorded by one
78    /// downward pass when the scan starts. `Node::parent` recovers a parent by
79    /// re-descending from the root, so an ancestor climb is quadratic in depth
80    /// and the scan climbs from more than a dozen call sites per visited node
81    /// (#1927, #3097).
82    pub ancestry: ParentIndex<'a>,
83    ordinary_type_imports: OrdinaryTypeImportCell,
84    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
85    class_ranges: Option<&'a ClassRangeIndex>,
86    pub line_starts: &'a [usize],
87    pub spec: &'a TargetSpec,
88    pub target_group: &'a HashSet<CodeUnit>,
89    pub has_proven_visible_type_target: bool,
90    uses_c_semantics: bool,
91    type_reference_component_names: HashSet<String>,
92    pub target_declaration_ranges: Vec<Range>,
93    target_macro_declaration_bytes: Vec<usize>,
94    pub bindings: LocalInferenceEngine<CppScanBinding>,
95    local_shadows: LocalInferenceEngine<()>,
96    using_enum_owners: ScopedUsingEnumOwners,
97    semantic_using_enum_owners: SemanticUsingEnumOwners,
98    needs_using_enum_member_resolution: bool,
99    pub hits: &'a mut BTreeSet<UsageHit>,
100    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
101    pub raw_match_count: &'a mut usize,
102    pub max_usages: usize,
103    pub external_hit_count: usize,
104    pub limit_exceeded: &'a mut bool,
105    pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
106    pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
107    lexical_scope_cache: LexicalScopeCache,
108    lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
109    member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
110    global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
111    receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
112}
113
114impl ScanCtx<'_> {
115    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
116        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
117    }
118}
119
120#[derive(Clone, Default)]
121pub struct EnclosingContext {
122    pub enclosing: Option<CodeUnit>,
123    pub owner: Option<CodeUnit>,
124}
125
126pub fn prepare_file(
127    cpp: &dyn CppSource,
128    token: QueryToken<'_>,
129    file: &ProjectFile,
130) -> Option<Arc<PreparedSyntaxTree>> {
131    cpp.prepared_syntax(token, file)
132}
133
134#[allow(clippy::too_many_arguments)]
135pub fn scan_prepared_file(
136    analyzer: &CppGraphSource<'_>,
137    visibility: &VisibilityIndex<'_>,
138    file: &ProjectFile,
139    prepared: &PreparedSyntaxTree,
140    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
141    class_ranges: Option<&ClassRangeIndex>,
142    spec: &TargetSpec,
143    target_group: &HashSet<CodeUnit>,
144    state: &mut ScanState<'_>,
145) {
146    if *state.limit_exceeded {
147        return;
148    }
149    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
150    let has_proven_visible_type_target = spec.kind == TargetKind::Type
151        && (target_group.iter().any(|target| {
152            same_logical_symbol(target, &spec.target)
153                && visibility.is_physically_visible(file, target)
154        }) || {
155            let candidates = visibility
156                .visible_identifier_candidates(file, spec.target.identifier())
157                .collect::<Vec<_>>();
158            visibility.c_tag_declaration_family_matches_target(
159                analyzer,
160                file,
161                &candidates,
162                &spec.target,
163            )
164        });
165    if spec.kind == TargetKind::Type
166        && !has_proven_visible_type_target
167        && visibility
168            .visible_identifier_candidates(file, spec.target.identifier())
169            .any(|candidate| {
170                candidate != &spec.target
171                    && !target_group.contains(candidate)
172                    && same_logical_symbol(candidate, &spec.target)
173                    && visibility.is_physically_visible(file, candidate)
174                    && !visibility.c_tag_declaration_family_matches_target(
175                        analyzer,
176                        file,
177                        std::slice::from_ref(&candidate),
178                        &spec.target,
179                    )
180            })
181    {
182        return;
183    }
184    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
185        analyzer.ranges(&spec.target)
186    } else {
187        Vec::new()
188    };
189    let target_declaration_ranges = if spec.kind == TargetKind::Type {
190        target_group
191            .iter()
192            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
193            .flat_map(|target| analyzer.ranges(target))
194            .collect()
195    } else if spec.target.source() == file {
196        if spec.kind == TargetKind::Macro {
197            macro_target_declaration_ranges.clone()
198        } else {
199            analyzer.ranges(&spec.target)
200        }
201    } else {
202        Vec::new()
203    };
204    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
205        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
206    } else {
207        Vec::new()
208    };
209    let type_reference_component_names = if spec.kind == TargetKind::Type {
210        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
211    } else {
212        HashSet::default()
213    };
214    if spec.kind == TargetKind::Type
215        && !file_may_reference_type_target(
216            prepared.tree().root_node(),
217            prepared.source(),
218            analyzer,
219            visibility,
220            file,
221            &spec.target,
222            &type_reference_component_names,
223        )
224    {
225        return;
226    }
227    let ordinary_type_imports = initialized_ordinary_type_imports(
228        prepared.tree().root_node(),
229        analyzer,
230        visibility,
231        file,
232        prepared.source(),
233    );
234    let external_hit_count = state
235        .hits
236        .iter()
237        .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
238        .count();
239    let mut ctx = ScanCtx {
240        analyzer: *analyzer,
241        visibility,
242        file,
243        source: prepared.source(),
244        ancestry: ParentIndex::new(prepared.tree().root_node()),
245        ordinary_type_imports,
246        recovered_sentinel_classes,
247        class_ranges,
248        line_starts: prepared.line_starts(),
249        spec,
250        target_group,
251        has_proven_visible_type_target,
252        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
253        type_reference_component_names,
254        target_declaration_ranges,
255        target_macro_declaration_bytes,
256        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
257        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
258        using_enum_owners: ScopedUsingEnumOwners::new(),
259        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
260        needs_using_enum_member_resolution,
261        hits: state.hits,
262        unproven_hits: state.unproven_hits,
263        raw_match_count: state.raw_match_count,
264        max_usages: state.max_usages,
265        external_hit_count,
266        limit_exceeded: state.limit_exceeded,
267        enclosing_cache: RefCell::new(HashMap::default()),
268        enclosing_owner_cache: RefCell::new(HashMap::default()),
269        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
270        lexical_free_function_cache: RefCell::new(HashMap::default()),
271        member_owner_cache: RefCell::new(HashMap::default()),
272        global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
273        receiver_canonical_type_cache: RefCell::new(HashMap::default()),
274    };
275    if needs_using_enum_member_resolution {
276        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
277    }
278    if spec.kind == TargetKind::Macro {
279        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
280        return;
281    }
282    scan_node(prepared.tree().root_node(), &mut ctx);
283}
284
285/// Scan a macro target without maintaining the declaration and lexical state
286/// required by ordinary C and C++ symbols. Macro activation comes entirely
287/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
288/// node makes generated tables needlessly expensive.
289fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
290    walk_named_tree_preorder(root, true, |node| {
291        if *ctx.limit_exceeded {
292            return WalkControl::Break;
293        }
294        maybe_record_macro_hit(node, ctx);
295        WalkControl::Continue
296    });
297}
298
299enum UsingEnumDeclarationScope {
300    Block,
301    Class(CodeUnit),
302    Namespace(Vec<String>),
303    UnsupportedClass,
304}
305
306fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
307    let mut current = ctx.ancestry.parent(node);
308    while let Some(parent) = current {
309        if matches!(
310            parent.kind(),
311            "compound_statement"
312                | "function_definition"
313                | "lambda_expression"
314                | "for_statement"
315                | "while_statement"
316                | "if_statement"
317        ) {
318            return UsingEnumDeclarationScope::Block;
319        }
320        if matches!(
321            parent.kind(),
322            "class_specifier" | "struct_specifier" | "union_specifier"
323        ) {
324            let resolution = enclosing_lexical_scope_components(
325                node,
326                &ctx.analyzer,
327                ctx.visibility,
328                ctx.file,
329                ctx.source,
330            );
331            if let LexicalScopeResolution::Resolved(components) = resolution
332                && let LexicalTypeResolution::Resolved { unit, .. } =
333                    ctx.visibility.resolve_type_components_lexically(
334                        &ctx.analyzer,
335                        ctx.file,
336                        &components,
337                        true,
338                        &[],
339                    )
340            {
341                return UsingEnumDeclarationScope::Class(unit);
342            }
343            return UsingEnumDeclarationScope::UnsupportedClass;
344        }
345        current = ctx.ancestry.parent(parent);
346    }
347    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
348}
349
350fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
351    let mut stack = vec![root];
352    while let Some(node) = stack.pop() {
353        if node.kind() == "using_declaration"
354            && let LexicalTypeResolution::Resolved { unit, .. } =
355                resolve_using_enum_declaration_owner(
356                    node,
357                    &ctx.analyzer,
358                    ctx.visibility,
359                    &ctx.ordinary_type_imports,
360                    ctx.file,
361                    ctx.source,
362                )
363        {
364            match using_enum_declaration_scope(node, ctx) {
365                UsingEnumDeclarationScope::Block => {}
366                UsingEnumDeclarationScope::Class(class) => {
367                    ctx.semantic_using_enum_owners.import_class(class, unit);
368                }
369                UsingEnumDeclarationScope::Namespace(namespace) => {
370                    ctx.semantic_using_enum_owners.import_namespace(
371                        namespace,
372                        node.start_byte(),
373                        unit,
374                    );
375                }
376                UsingEnumDeclarationScope::UnsupportedClass => {}
377            }
378        }
379        push_named_children_reversed(node, &mut stack);
380    }
381}
382
383fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
384    if *ctx.limit_exceeded {
385        return;
386    }
387    let enters_scope = matches!(
388        node.kind(),
389        "compound_statement"
390            | "function_definition"
391            | "lambda_expression"
392            | "for_statement"
393            | "for_range_loop"
394            | "while_statement"
395            | "if_statement"
396    );
397    let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
398        && (enters_scope
399            || matches!(
400                node.kind(),
401                "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
402            ));
403    if enters_scope {
404        ctx.bindings.enter_scope();
405        ctx.local_shadows.enter_scope();
406    }
407    if enters_using_enum_scope {
408        ctx.using_enum_owners.enter_scope();
409    }
410
411    seed_declarations(node, ctx);
412    maybe_record_hit(node, ctx);
413
414    let translation_unit = node.kind() == "translation_unit";
415    let mut fractured_function_scope = false;
416    let mut cursor = node.walk();
417    for child in node.named_children(&mut cursor) {
418        if translation_unit && fractured_function_scope && child.kind() == "ERROR" {
419            ctx.bindings.exit_scope();
420            ctx.local_shadows.exit_scope();
421            fractured_function_scope = false;
422        }
423        scan_node(child, ctx);
424        if *ctx.limit_exceeded {
425            break;
426        }
427        if translation_unit
428            && macro_fractured_function(child)
429            && macro_fracture_boundary(child).is_some()
430        {
431            if fractured_function_scope {
432                ctx.bindings.exit_scope();
433                ctx.local_shadows.exit_scope();
434            }
435            ctx.bindings.enter_scope();
436            ctx.local_shadows.enter_scope();
437            seed_fractured_function_prefix(child, ctx);
438            fractured_function_scope = true;
439        }
440    }
441    if fractured_function_scope {
442        ctx.bindings.exit_scope();
443        ctx.local_shadows.exit_scope();
444    }
445
446    if enters_scope {
447        ctx.bindings.exit_scope();
448        ctx.local_shadows.exit_scope();
449    }
450    if enters_using_enum_scope {
451        ctx.using_enum_owners.exit_scope();
452    }
453}
454
455fn macro_fractured_function(node: Node<'_>) -> bool {
456    if node.kind() != "function_definition" {
457        return false;
458    }
459    let Some(body) = node.child_by_field_name("body") else {
460        return false;
461    };
462    let mut stack = vec![body];
463    while let Some(current) = stack.pop() {
464        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
465            return true;
466        }
467        let mut cursor = current.walk();
468        stack.extend(current.named_children(&mut cursor));
469    }
470    false
471}
472
473fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
474    let first_orphan = node.next_named_sibling()?;
475    if !matches!(
476        first_orphan.kind(),
477        "expression_statement"
478            | "if_statement"
479            | "for_statement"
480            | "while_statement"
481            | "do_statement"
482            | "return_statement"
483    ) {
484        return None;
485    }
486    let mut sibling = Some(first_orphan);
487    while let Some(current) = sibling {
488        if current.kind() == "ERROR" {
489            return Some(current.end_byte());
490        }
491        if matches!(
492            current.kind(),
493            "function_definition" | "declaration" | "type_definition"
494        ) {
495            return None;
496        }
497        sibling = current.next_named_sibling();
498    }
499    None
500}
501
502fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
503    let cutoff = node.end_byte().saturating_sub(1);
504    seed_fractured_active_path(node, cutoff, ctx);
505}
506
507fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
508    if node.start_byte() >= cutoff {
509        return;
510    }
511    let enters_scope = matches!(
512        node.kind(),
513        "compound_statement"
514            | "function_definition"
515            | "lambda_expression"
516            | "for_range_loop"
517            | "for_statement"
518            | "while_statement"
519            | "if_statement"
520            | "class_specifier"
521            | "struct_specifier"
522            | "union_specifier"
523    );
524    if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
525        return;
526    }
527    seed_declarations(node, ctx);
528    let mut cursor = node.walk();
529    for child in node.named_children(&mut cursor) {
530        if child.start_byte() >= cutoff {
531            break;
532        }
533        seed_fractured_active_path(child, cutoff, ctx);
534    }
535}
536
537fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
538    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
539        || (ctx.spec.kind != TargetKind::Type
540            && indexed_recovered_class_field_declaration(node, ctx))
541    {
542        return;
543    }
544    match node.kind() {
545        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
546        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
547        "for_range_loop" => seed_range_binding(node, ctx),
548        "expression_statement" => seed_function_macro_local_binding(node, ctx),
549        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
550        "using_declaration" => seed_using_enum(node, ctx),
551        _ => {}
552    }
553}
554
555fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
556    let Some(binding) = ctx
557        .visibility
558        .function_macro_local_binding(ctx.file, node, ctx.source)
559    else {
560        return;
561    };
562    if ctx.spec.kind == TargetKind::Type {
563        ctx.bindings.declare_shadow(binding.name);
564        return;
565    }
566    let normalized = normalize_cpp_type_name(&binding.type_name);
567    let unit = binding
568        .type_node
569        .and_then(|type_node| {
570            ctx.visibility
571                .resolve_type_node_result(ctx.file, type_node, ctx.source)
572                .ok()
573                .flatten()
574        })
575        .or_else(|| {
576            ctx.visibility
577                .canonical_type_for_reference(ctx.file, &normalized)
578        })
579        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
580    ctx.bindings.seed_symbol(
581        binding.name,
582        CppScanBinding::from_type_name(
583            normalized,
584            unit,
585            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
586        ),
587    );
588}
589
590fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
591    let Some(binding) =
592        ctx.visibility
593            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
594    else {
595        return;
596    };
597    let normalized = normalize_cpp_type_name(&binding.type_name);
598    let unit = binding.proven_unit.clone().or_else(|| {
599        binding
600            .type_node
601            .and_then(|type_node| {
602                ctx.visibility
603                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
604                    .ok()
605                    .flatten()
606            })
607            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
608    });
609    if let Some(unit) = unit {
610        ctx.bindings.seed_symbol(
611            binding.name,
612            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
613        );
614    }
615}
616
617fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
618    if node.kind() != "declaration"
619        || !has_function_scope_ancestor(node)
620        || has_ancestor_kind(node, "lambda_expression")
621        || !(has_recovered_class_shape_ancestor(node)
622            || has_malformed_wrapper_function_definition_ancestor(node))
623    {
624        return false;
625    }
626    let context = enclosing_context(node, ctx);
627    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
628        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
629}
630
631fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
632    if !ctx.needs_using_enum_member_resolution {
633        return;
634    }
635    if let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
636        node,
637        &ctx.analyzer,
638        ctx.visibility,
639        &ctx.ordinary_type_imports,
640        ctx.file,
641        ctx.source,
642    ) && matches!(
643        using_enum_declaration_scope(node, ctx),
644        UsingEnumDeclarationScope::Block
645    ) {
646        ctx.using_enum_owners.import(unit);
647    }
648}
649
650fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
651    // Class and local-struct fields are resolved from their enclosing owner
652    // when they appear as an unqualified receiver. Seeding them into the
653    // function-wide binding scope would make same-spelled fields from sibling
654    // owners overwrite one another before the owner-aware path runs.
655    if node.kind() == "field_declaration" {
656        return;
657    }
658    let type_node = node
659        .child_by_field_name("type")
660        .or_else(|| first_type_child(node));
661    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
662    let mut cursor = node.walk();
663    for child in node.named_children(&mut cursor) {
664        let declarator = if child.kind() == "init_declarator" {
665            child.child_by_field_name("declarator")
666        } else if is_declarator_node(child) {
667            Some(child)
668        } else {
669            None
670        };
671        let Some(declarator) = declarator else {
672            continue;
673        };
674        let Some(name) = extract_variable_name(declarator, ctx.source) else {
675            continue;
676        };
677        if declarator.kind() == "function_declarator"
678            && !constructor_style_local_declaration(
679                ctx.visibility,
680                ctx.file,
681                ctx.source,
682                declarator,
683                type_text.as_deref(),
684                &ctx.bindings,
685            )
686        {
687            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
688                ctx.local_shadows.declare_shadow(name);
689            }
690            continue;
691        }
692        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
693            ctx.local_shadows.declare_shadow(name.clone());
694        }
695        if ctx.spec.kind == TargetKind::Type {
696            ctx.bindings.declare_shadow(name);
697            continue;
698        }
699        let value = child.child_by_field_name("value");
700        seed_binding_from_type_or_value(&name, type_node, value, ctx);
701    }
702}
703
704fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
705    if !parameter_belongs_to_callable_scope(node) {
706        return;
707    }
708    let Some(declarator) = node.child_by_field_name("declarator") else {
709        return;
710    };
711    let Some(name) = extract_variable_name(declarator, ctx.source) else {
712        return;
713    };
714    if has_function_scope_ancestor(node) {
715        ctx.local_shadows.declare_shadow(name.clone());
716    }
717    if ctx.spec.kind == TargetKind::Type {
718        ctx.bindings.declare_shadow(name);
719        return;
720    }
721    let type_node = node
722        .child_by_field_name("type")
723        .or_else(|| first_type_child(node));
724    seed_binding_from_type_or_value(&name, type_node, None, ctx);
725}
726
727fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
728    let Some(declarator) = node.child_by_field_name("declarator") else {
729        return;
730    };
731    let Some(name) = extract_variable_name(declarator, ctx.source) else {
732        return;
733    };
734    if has_function_scope_ancestor(node) {
735        ctx.local_shadows.declare_shadow(name.clone());
736    }
737    if ctx.spec.kind == TargetKind::Type {
738        ctx.bindings.declare_shadow(name);
739        return;
740    }
741    let type_node = node
742        .child_by_field_name("type")
743        .or_else(|| first_type_child(node));
744    seed_binding_from_type_or_value(&name, type_node, None, ctx);
745}
746
747fn has_function_scope_ancestor(node: Node<'_>) -> bool {
748    let mut current = node.parent();
749    while let Some(parent) = current {
750        // Error recovery can wrap a complete namespace in a bogus outer
751        // `function_definition` (for example when an object-like namespace
752        // macro is parsed as a return type).  Declarations below that
753        // namespace are still namespace-scoped: do not let the malformed
754        // callable envelope seed them as local shadows.  A real function
755        // body is encountered before its enclosing namespace, so this keeps
756        // ordinary local binding detection unchanged.
757        if parent.kind() == "namespace_definition" {
758            return false;
759        }
760        if parent.kind() == "function_definition" {
761            // The malformed sentinel envelope is not a callable scope. Its
762            // body may contain a recovered namespace (or, in a smaller error
763            // tree, only an ERROR node standing in for that namespace), so
764            // declarations directly below it must remain namespace-scoped.
765            // Real nested functions are encountered first and still seed
766            // ordinary local bindings.
767            return !is_malformed_wrapper_function_definition(parent);
768        }
769        if parent.kind() == "lambda_expression" {
770            return true;
771        }
772        current = parent.parent();
773    }
774    false
775}
776
777fn seed_binding_from_type_or_value(
778    name: &str,
779    type_node: Option<Node<'_>>,
780    value: Option<Node<'_>>,
781    ctx: &mut ScanCtx<'_>,
782) {
783    if name.is_empty() {
784        return;
785    }
786    let resolved = type_node
787        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
788        .map(|node| {
789            let text = node_text(node, ctx.source);
790            let name = normalize_cpp_type_name(text);
791            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
792                return CppScanBinding::from_type_name(
793                    name,
794                    Some(unit),
795                    cpp_type_text_pointer_depth(text),
796                );
797            }
798            // Bare type names need lexical ownership before the coarse visible-name
799            // fallback (two namespaces can each declare `CopyResult`). Template
800            // references keep the specialization-aware resolver first because a
801            // component-only lexical lookup cannot rank partial specializations.
802            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
803                if cpp_template_reference_arguments(node, ctx.source).is_some() {
804                    return None;
805                }
806                match enclosing_lexical_scope_components(
807                    node,
808                    &ctx.analyzer,
809                    ctx.visibility,
810                    ctx.file,
811                    ctx.source,
812                ) {
813                    LexicalScopeResolution::Resolved(scope) => Some(scope),
814                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
815                }
816            });
817            let unit = lexical_scope
818                .as_deref()
819                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
820                .or_else(|| {
821                    match ctx
822                        .visibility
823                        .resolve_type_node_result(ctx.file, node, ctx.source)
824                    {
825                        Ok(Some(unit)) => Some(unit),
826                        Ok(None) => ctx
827                            .visibility
828                            .canonical_type_for_reference(ctx.file, &name)
829                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
830                        Err(_) => None,
831                    }
832                });
833            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
834        })
835        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
836
837    if let Some(resolved) = resolved {
838        ctx.bindings.seed_symbol(name.to_string(), resolved);
839    } else if let Some(value) = value
840        && value.kind() == "identifier"
841    {
842        ctx.bindings
843            .alias_symbol(name.to_string(), node_text(value, ctx.source));
844    } else {
845        ctx.bindings.declare_shadow(name.to_string());
846    }
847}
848
849fn resolve_seed_type_node_lexically(
850    node: Node<'_>,
851    ctx: &ScanCtx<'_>,
852    scope: &[String],
853) -> Option<CodeUnit> {
854    let (components, global) = type_reference_components(node, ctx.source)?;
855    let resolution = match scan_owner_type(ctx) {
856        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
857            &ctx.analyzer,
858            ctx.file,
859            &components,
860            global,
861            scope,
862            target,
863        ),
864        None => ctx.visibility.resolve_type_components_lexically(
865            &ctx.analyzer,
866            ctx.file,
867            &components,
868            global,
869            scope,
870        ),
871    };
872    match resolution {
873        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
874        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
875    }
876}
877
878/// The type this scan is asking about: the owner of a member target, or the
879/// target itself when it is a type.
880///
881/// C headers routinely declare one typedef name once per branch of an
882/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
883/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
884/// are alternate spellings of one name rather than competing declarations, and
885/// canonicalizing without the scan's own type identity picks between them by
886/// declaration order. That ordering can bind a receiver to the branch that does
887/// not declare the queried member, which drops every reference in the branch
888/// that does. Naming the identity keeps the target-preserving rule the forward
889/// resolver already applies (`unique_type_candidate_preserving_target`).
890fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
891    // Target-preserving lookup can reconcile logical declarations across files.
892    // A receiver must not borrow an owner from a header its caller cannot see.
893    ctx.spec
894        .owner
895        .as_ref()
896        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
897}
898
899const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
900
901fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
902    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
903}
904
905fn infer_type_from_value_with_budget(
906    node: Node<'_>,
907    ctx: &ScanCtx<'_>,
908    remaining_call_depth: usize,
909) -> Option<CppScanBinding> {
910    match node.kind() {
911        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
912            infer_cpp_initializer_binding(
913                &ctx.analyzer,
914                ctx.visibility,
915                ctx.file,
916                ctx.source,
917                node,
918                None,
919            )
920        }
921        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
922            &ctx.analyzer,
923            ctx.visibility,
924            ctx.file,
925            ctx.source,
926            node,
927            Some(&|receiver, source| {
928                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
929            }),
930        ),
931        "initializer_list" => None,
932        "identifier" => {
933            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
934            resolved
935                .as_precise()?
936                .iter()
937                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
938                .cloned()
939        }
940        _ => {
941            let text = node_text(node, ctx.source);
942            let name = normalize_cpp_type_name(text);
943            ctx.visibility
944                .resolve_type(ctx.file, &name)
945                .map(|unit| CppScanBinding::from_unit(unit, 0))
946        }
947    }
948}
949
950/// Whether a member named `identifier` can be spelled anywhere in this file's
951/// syntax.
952///
953/// Both member scans below -- [`maybe_record_method_hit`] and
954/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
955/// of a syntax node against the target's member name, so a file whose syntax
956/// never spells that name yields neither a proven nor an unproven hit. That
957/// makes this an exact admission test rather than an approximation: candidate
958/// discovery can drop such a file without narrowing what the query proves.
959///
960/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
961/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
962/// replacement, which
963/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
964/// sentinel parse, and an `ERROR` region -- so those admit on containment
965/// instead of on a token match. A `comment` is the one subtree no scan reads,
966/// so it is skipped.
967///
968/// Defined only for a plain identifier: [`name_matches_callable`] widens the
969/// comparison for an `operator` name to the `operator` token alone, and a
970/// destructor or conversion-function name is not spelled the way it is
971/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
972/// that condition.
973pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
974    debug_assert!(
975        cpp_member_is_spelled_at_references(identifier),
976        "member admission is defined only for a plain identifier: {identifier:?}"
977    );
978    let mut spelled = false;
979    walk_named_tree_preorder(root, true, |node| match node.kind() {
980        "comment" => WalkControl::SkipChildren,
981        "identifier"
982        | "field_identifier"
983        | "type_identifier"
984        | "namespace_identifier"
985        | "statement_identifier" => {
986            if node_text(node, source).trim() == identifier {
987                spelled = true;
988                WalkControl::Break
989            } else {
990                WalkControl::SkipChildren
991            }
992        }
993        "preproc_arg" | "ERROR" => {
994            if node_text(node, source).contains(identifier) {
995                spelled = true;
996                WalkControl::Break
997            } else {
998                // The node's own text covers every descendant's bytes, so a
999                // miss here is a miss for the whole subtree.
1000                WalkControl::SkipChildren
1001            }
1002        }
1003        _ => WalkControl::Continue,
1004    });
1005    spelled
1006}
1007
1008/// Whether every reference to a member named `identifier` must spell it.
1009///
1010/// An `operator` name is matched by [`name_matches_callable`] through the bare
1011/// `operator` token, and a destructor or conversion function is recorded under
1012/// a name its call sites do not write, so neither admits a spelling test.
1013pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1014    let mut characters = identifier.chars();
1015    characters
1016        .next()
1017        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1018        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1019        && !identifier.starts_with("operator")
1020}
1021
1022fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1023    match ctx.spec.kind {
1024        TargetKind::Type => maybe_record_type_hit(node, ctx),
1025        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1026        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1027        TargetKind::Method => maybe_record_method_hit(node, ctx),
1028        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1029        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1030        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1031    }
1032}
1033
1034fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1035    if node_text(node, ctx.source) != ctx.spec.member_name {
1036        return;
1037    }
1038    if is_ordinary_macro_reference_node(node) {
1039        if ctx.visibility.macro_binding_matches_target_declaration_at(
1040            ctx.file,
1041            &ctx.spec.member_name,
1042            node.start_byte(),
1043            ctx.spec.target.source(),
1044            &ctx.target_macro_declaration_bytes,
1045        ) {
1046            *ctx.raw_match_count += 1;
1047            push_hit(node, ctx);
1048            return;
1049        }
1050        match ctx.visibility.resolve_ordinary_macro_reference(
1051            &ctx.analyzer,
1052            ctx.file,
1053            node,
1054            ctx.source,
1055        ) {
1056            OrdinaryMacroReferenceResolution::Resolved(unit)
1057                if ctx.target_group.contains(&unit) =>
1058            {
1059                *ctx.raw_match_count += 1;
1060                push_hit(node, ctx);
1061            }
1062            OrdinaryMacroReferenceResolution::Ambiguous
1063                if ctx
1064                    .visibility
1065                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1066            {
1067                *ctx.raw_match_count += 1;
1068                push_unproven_hit(node, ctx);
1069            }
1070            OrdinaryMacroReferenceResolution::Resolved(_)
1071            | OrdinaryMacroReferenceResolution::Ambiguous
1072            | OrdinaryMacroReferenceResolution::Missing => {}
1073        }
1074        return;
1075    }
1076    if !matches!(
1077        node.kind(),
1078        "identifier"
1079            | "field_identifier"
1080            | "type_identifier"
1081            | "namespace_identifier"
1082            | "preproc_arg"
1083    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1084        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1085            && parent
1086                .child_by_field_name("name")
1087                .is_some_and(|name| same_node(name, node))
1088    }) {
1089        return;
1090    }
1091    if ctx.visibility.macro_binding_matches_target_declaration_at(
1092        ctx.file,
1093        &ctx.spec.member_name,
1094        node.start_byte(),
1095        ctx.spec.target.source(),
1096        &ctx.target_macro_declaration_bytes,
1097    ) {
1098        *ctx.raw_match_count += 1;
1099        push_hit(node, ctx);
1100    } else if ctx.visibility.macro_name_may_be_bound_at(
1101        ctx.file,
1102        &ctx.spec.member_name,
1103        node.start_byte(),
1104    ) && ctx
1105        .visibility
1106        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1107    {
1108        *ctx.raw_match_count += 1;
1109        push_unproven_hit(node, ctx);
1110    }
1111}
1112
1113fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1114    let kind = node.kind();
1115    if ctx.uses_c_semantics
1116        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1117            .is_some()
1118    {
1119        return;
1120    }
1121    if kind == "preproc_arg" {
1122        maybe_record_object_macro_replacement_type_hits(node, ctx);
1123        return;
1124    }
1125    if let Some(type_node) = ctx
1126        .visibility
1127        .function_macro_type_argument(ctx.file, node, ctx.source)
1128    {
1129        if ctx
1130            .local_shadows
1131            .is_shadowed(node_text(type_node, ctx.source))
1132        {
1133            return;
1134        }
1135        if let LexicalTypeResolution::Resolved {
1136            unit, candidates, ..
1137        } = resolve_type_node_lexically_for_target(
1138            type_node,
1139            &ctx.analyzer,
1140            ctx.visibility,
1141            &ctx.ordinary_type_imports,
1142            ctx.file,
1143            ctx.source,
1144            &ctx.spec.target,
1145            Some(&ctx.lexical_scope_cache),
1146            ctx.recovered_sentinel_scope(type_node).as_deref(),
1147        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1148        {
1149            *ctx.raw_match_count += 1;
1150            push_type_hit(type_node, ctx);
1151        }
1152        return;
1153    }
1154    let recovered_exported_class_base =
1155        matches!(
1156            kind,
1157            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1158        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1159    if kind == "field_declaration"
1160        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1161    {
1162        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1163        return;
1164    }
1165    if kind == "qualified_identifier"
1166        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1167    {
1168        // A member-pointer owner can itself end in a nested alias, as in
1169        // `type_identity<T>::type::*`. Resolving the complete owner
1170        // canonicalizes that alias to its underlying type and loses the alias
1171        // declaration that inverse lookup is targeting. Retain every
1172        // structurally proven qualifier component before asking for the
1173        // canonical owner type.
1174        if ctx
1175            .analyzer
1176            .type_alias_provider()
1177            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1178            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1179            && let Some(terminal) = owner.nodes.last().copied()
1180        {
1181            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1182                return;
1183            }
1184            if ctx.visibility.external_type_candidate_visible_in_context(
1185                &ctx.analyzer,
1186                ctx.file,
1187                &ctx.spec.target,
1188                terminal,
1189            ) || ctx
1190                .visibility
1191                .dependent_member_pointer_alias_visible_in_context(
1192                    &ctx.analyzer,
1193                    ctx.file,
1194                    &ctx.spec.target,
1195                    &owner.names,
1196                    terminal,
1197                )
1198            {
1199                *ctx.raw_match_count += 1;
1200                push_type_hit(terminal, ctx);
1201            }
1202            return;
1203        }
1204        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1205            *ctx.raw_match_count += 1;
1206            for scope in scopes {
1207                push_type_hit(scope, ctx);
1208            }
1209            return;
1210        }
1211        return;
1212    }
1213    if kind == "pointer_expression"
1214        && let Some(value) = qualified_callable_value(node)
1215        && let Some(scope) =
1216            target_guided_unproven_qualified_value_owner_scope(value.qualified, ctx)
1217    {
1218        *ctx.raw_match_count += 1;
1219        push_unproven_hit(scope, ctx);
1220        return;
1221    }
1222    if kind == "call_expression" {
1223        maybe_record_direct_temporary_type_hit(node, ctx);
1224        return;
1225    }
1226    if !matches!(
1227        kind,
1228        "identifier"
1229            | "namespace_identifier"
1230            | "qualified_identifier"
1231            | "scoped_identifier"
1232            | "scoped_type_identifier"
1233            | "template_function"
1234            | "template_type"
1235            | "type_descriptor"
1236            | "type_identifier"
1237            | "using_declaration"
1238    ) {
1239        return;
1240    }
1241    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1242    let recovered_qualified_friend =
1243        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1244    if !recovered_type
1245        && !recovered_qualified_friend
1246        && !recovered_exported_class_base
1247        && matches!(
1248            kind,
1249            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1250        )
1251        && !type_reference_components_may_name_target(node, ctx)
1252    {
1253        return;
1254    }
1255    if ctx.ancestry.parent(node).is_some_and(|parent| {
1256        parent.kind() == "operator_cast"
1257            && parent
1258                .child_by_field_name("type")
1259                .is_some_and(|target| same_node(target, node))
1260    }) {
1261        return;
1262    }
1263    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1264        *ctx.raw_match_count += 1;
1265        push_type_hit(hit, ctx);
1266        return;
1267    }
1268    if matches!(kind, "identifier" | "template_function") && call_for_function_node(node).is_some()
1269    {
1270        return;
1271    }
1272    if kind == "using_declaration" {
1273        let (resolution, type_node) =
1274            if let Some(type_node) = using_enum_declaration_type_node(node) {
1275                (
1276                    resolve_using_enum_declaration_owner(
1277                        node,
1278                        &ctx.analyzer,
1279                        ctx.visibility,
1280                        &ctx.ordinary_type_imports,
1281                        ctx.file,
1282                        ctx.source,
1283                    ),
1284                    type_node,
1285                )
1286            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1287                (
1288                    resolve_ordinary_using_declaration_owner(
1289                        node,
1290                        &ctx.analyzer,
1291                        ctx.visibility,
1292                        ctx.file,
1293                        ctx.source,
1294                    ),
1295                    type_node,
1296                )
1297            } else {
1298                return;
1299            };
1300        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1301            && same_visible_symbol(&unit, &ctx.spec.target)
1302        {
1303            *ctx.raw_match_count += 1;
1304            push_type_hit(type_node, ctx);
1305        }
1306        return;
1307    }
1308    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1309        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1310            || local_type_name_shadows(node, ctx)
1311        {
1312            return;
1313        }
1314        if let LexicalTypeResolution::Resolved {
1315            unit, candidates, ..
1316        } = resolve_type_node_lexically_for_target(
1317            node,
1318            &ctx.analyzer,
1319            ctx.visibility,
1320            &ctx.ordinary_type_imports,
1321            ctx.file,
1322            ctx.source,
1323            &ctx.spec.target,
1324            Some(&ctx.lexical_scope_cache),
1325            ctx.recovered_sentinel_scope(node).as_deref(),
1326        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1327        {
1328            *ctx.raw_match_count += 1;
1329            push_type_hit(node, ctx);
1330        }
1331        return;
1332    }
1333    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1334        // A missing `::` can put either the macro or the real type in the
1335        // recovered scope. Resolve both candidates against this inverse
1336        // target before choosing one: a unique match is routed through the
1337        // ordinary target-guided path, while two distinct matches are
1338        // ambiguous and must not invent a hit. With no target match, retain
1339        // the existing recovered-scope path below for its conservative
1340        // fallback behaviour.
1341        let mut matching = Vec::new();
1342        for candidate in [node, type_node] {
1343            if matching
1344                .iter()
1345                .any(|existing| same_node(*existing, candidate))
1346            {
1347                continue;
1348            }
1349            if let LexicalTypeResolution::Resolved {
1350                unit, candidates, ..
1351            } = resolve_type_node_lexically_for_target(
1352                candidate,
1353                &ctx.analyzer,
1354                ctx.visibility,
1355                &ctx.ordinary_type_imports,
1356                ctx.file,
1357                ctx.source,
1358                &ctx.spec.target,
1359                Some(&ctx.lexical_scope_cache),
1360                ctx.recovered_sentinel_scope(candidate).as_deref(),
1361            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1362            {
1363                matching.push(candidate);
1364            }
1365        }
1366        match matching.as_slice() {
1367            [candidate] if !same_node(*candidate, node) => {
1368                maybe_record_type_hit(*candidate, ctx);
1369                return;
1370            }
1371            [candidate] if same_node(*candidate, node) => {}
1372            [] => {}
1373            _ => return,
1374        }
1375    }
1376    if !recovered_type
1377        && !matches!(
1378            kind,
1379            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1380        )
1381    {
1382        return;
1383    }
1384    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1385        components.len() == 1 && !global && local_type_name_shadows(node, ctx)
1386    }) {
1387        return;
1388    }
1389    if !recovered_type
1390        && !recovered_qualified_friend
1391        && !recovered_exported_class_base
1392        && ctx.ancestry.parent(node).is_some_and(|parent| {
1393            parent.kind() == "alias_declaration"
1394                && parent
1395                    .child_by_field_name("name")
1396                    .is_some_and(|name| same_node(name, node))
1397        })
1398    {
1399        return;
1400    }
1401    #[cfg(any(test, feature = "test-support"))]
1402    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1403    if !recovered_type
1404        && !recovered_qualified_friend
1405        && !recovered_exported_class_base
1406        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1407        && is_declaration_name(node)
1408        && let Some(owners) = out_of_line_member_definition_owner(
1409            &ctx.analyzer,
1410            ctx.visibility,
1411            ctx.file,
1412            ctx.source,
1413            node,
1414        )
1415    {
1416        *ctx.raw_match_count += 1;
1417        let mut matched_owner = false;
1418        for (owner_node, owner) in owners.owners {
1419            if same_visible_symbol(&owner, &ctx.spec.target) {
1420                matched_owner = true;
1421                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1422            }
1423        }
1424        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1425            for scope in scopes {
1426                push_hit(scope, ctx);
1427            }
1428        } else if !matched_owner
1429            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1430        {
1431            push_unproven_hit(scope, ctx);
1432        }
1433        return;
1434    }
1435    if !recovered_type
1436        && !recovered_qualified_friend
1437        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1438        && is_declaration_name(node)
1439        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1440    {
1441        *ctx.raw_match_count += 1;
1442        push_type_hit(owner, ctx);
1443        return;
1444    }
1445    // A qualified template-id is represented as a qualified_identifier whose
1446    // name child is the template_type. Resolve the complete qualified
1447    // reference from that inner template node below; handling the outer node
1448    // independently would either lose the qualifier or emit a duplicate,
1449    // wider hit range.
1450    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1451        && matches!(
1452            node.kind(),
1453            "qualified_identifier" | "scoped_type_identifier"
1454        )
1455        && node
1456            .child_by_field_name("name")
1457            .is_some_and(|name| name.kind() == "template_type")
1458    {
1459        return;
1460    }
1461    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1462        *ctx.raw_match_count += 1;
1463        push_unproven_hit(scope, ctx);
1464        return;
1465    }
1466    if !recovered_type
1467        && !is_nested_type_node(node)
1468        && matches!(
1469            node.kind(),
1470            "qualified_identifier" | "scoped_type_identifier"
1471        )
1472        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1473    {
1474        *ctx.raw_match_count += 1;
1475        for scope in scopes {
1476            push_type_hit(scope, ctx);
1477        }
1478        return;
1479    }
1480    if !recovered_type && is_nested_type_node(node) {
1481        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1482            *ctx.raw_match_count += 1;
1483            push_type_hit(hit, ctx);
1484            return;
1485        }
1486        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1487            *ctx.raw_match_count += 1;
1488            push_type_hit(hit, ctx);
1489            return;
1490        }
1491        // A concrete template specialization can be absent from the coarse
1492        // per-file component-name index: that index contains the primary name
1493        // while this recovered child may expose only the malformed template
1494        // leaf.  Resolve the complete enclosing template before applying the
1495        // component prefilter so an exact target candidate can prove this
1496        // reference.  Non-specialization targets retain the cheap gate.
1497        let nested_template = if node.kind() == "template_type" {
1498            Some(node)
1499        } else {
1500            ctx.ancestry.parent(node).filter(|parent| {
1501                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1502            })
1503        };
1504        // Concrete specializations need their nested template-id inspected.
1505        // An ordinary alias application does too when it is itself the scope
1506        // of a member-qualified type, because the enclosing node denotes the
1507        // member rather than the alias. In every other primary/alias shape the
1508        // enclosing structured type owns the hit range; descending would emit
1509        // a duplicate terminal subrange.
1510        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1511            let enclosing_qualified_type_owns_range =
1512                ctx.ancestry.parent(template).is_some_and(|parent| {
1513                    matches!(
1514                        parent.kind(),
1515                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1516                    ) && parent.child_by_field_name("name") == Some(template)
1517                });
1518            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1519                return false;
1520            };
1521            let Some(name) = template_reference_name_node(template) else {
1522                return false;
1523            };
1524            let alias_candidates = ctx
1525                .visibility
1526                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1527                .filter(|candidate| alias_provider.is_type_alias(candidate))
1528                .cloned()
1529                .collect::<Vec<_>>();
1530            let direct_target_alias_visible = !alias_candidates
1531                .iter()
1532                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1533                || ctx.visibility.external_type_candidate_visible_in_context(
1534                    &ctx.analyzer,
1535                    ctx.file,
1536                    &ctx.spec.target,
1537                    template,
1538                );
1539            !enclosing_qualified_type_owns_range
1540                && direct_target_alias_visible
1541                && template_reference_candidates_select_target(
1542                    template,
1543                    &alias_candidates,
1544                    &ctx.analyzer,
1545                    ctx.visibility,
1546                    ctx.file,
1547                    ctx.source,
1548                    &ctx.spec.target,
1549                )
1550        });
1551        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1552            return;
1553        }
1554        if nested_alias_qualifier {
1555            let template = nested_template.expect("a nested alias qualifier has a template node");
1556            *ctx.raw_match_count += 1;
1557            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1558                .unwrap_or_else(|| type_reference_hit_node(template));
1559            push_type_hit(hit, ctx);
1560            return;
1561        }
1562        // For concrete specializations, the visible identifier index can
1563        // already contain the exact template spelling even when lexical
1564        // resolution rejects the recovered child (the child itself has no
1565        // argument list).  That exact candidate is sufficient structured
1566        // evidence: retain the narrow template-name leaf and avoid emitting
1567        // the entire template-id range.
1568        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1569            && let Some(template) = nested_template
1570            && template_type_component_preserves_target(
1571                template,
1572                &ctx.visibility
1573                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1574                    .cloned()
1575                    .collect::<Vec<_>>(),
1576                ctx,
1577            )
1578        {
1579            *ctx.raw_match_count += 1;
1580            let hit = template
1581                .child_by_field_name("name")
1582                .filter(|name| name.kind() == "type_identifier")
1583                .unwrap_or(template);
1584            push_type_hit(hit, ctx);
1585            return;
1586        }
1587        if let Some(template) = nested_template
1588            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1589        {
1590            *ctx.raw_match_count += 1;
1591            let hit =
1592                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1593                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1594                        template
1595                            .child_by_field_name("name")
1596                            .filter(|name| name.kind() == "type_identifier")
1597                            .unwrap_or(template)
1598                    } else {
1599                        type_reference_hit_node(template)
1600                    }
1601                });
1602            push_type_hit(hit, ctx);
1603        }
1604        return;
1605    }
1606    if !recovered_type && let Some(call) = call_for_function_node(node) {
1607        let direct_target = resolve_qualified_call_target(
1608            call,
1609            node,
1610            &ctx.analyzer,
1611            ctx.visibility,
1612            &ctx.ordinary_type_imports,
1613            ctx.file,
1614            ctx.source,
1615        );
1616        if matches!(direct_target, BareCallTargetResolution::Type(_))
1617            && let LexicalTypeResolution::Resolved {
1618                unit, candidates, ..
1619            } = resolve_type_node_lexically_for_target(
1620                node,
1621                &ctx.analyzer,
1622                ctx.visibility,
1623                &ctx.ordinary_type_imports,
1624                ctx.file,
1625                ctx.source,
1626                &ctx.spec.target,
1627                Some(&ctx.lexical_scope_cache),
1628                ctx.recovered_sentinel_scope(node).as_deref(),
1629            )
1630            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1631        {
1632            *ctx.raw_match_count += 1;
1633            push_type_hit(type_reference_hit_node(node), ctx);
1634        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1635            *ctx.raw_match_count += 1;
1636            for scope in scopes {
1637                push_type_hit(scope, ctx);
1638            }
1639        } else if let Some(scope) = target_guided_unproven_qualified_value_owner_scope(node, ctx) {
1640            *ctx.raw_match_count += 1;
1641            push_unproven_hit(scope, ctx);
1642        }
1643        return;
1644    }
1645    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1646        *ctx.raw_match_count += 1;
1647        if proven {
1648            push_type_hit(hit, ctx);
1649        } else {
1650            push_unproven_hit(hit, ctx);
1651        }
1652        return;
1653    }
1654    if !recovered_type
1655        && !recovered_qualified_friend
1656        && !recovered_exported_class_base
1657        && is_declaration_name(node)
1658    {
1659        let mut matched_owner = false;
1660        if let Some(owners) = out_of_line_member_definition_owner(
1661            &ctx.analyzer,
1662            ctx.visibility,
1663            ctx.file,
1664            ctx.source,
1665            node,
1666        ) {
1667            for (owner_node, owner) in owners.owners {
1668                if same_visible_symbol(&owner, &ctx.spec.target) {
1669                    matched_owner = true;
1670                    *ctx.raw_match_count += 1;
1671                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1672                }
1673            }
1674        }
1675        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1676            *ctx.raw_match_count += 1;
1677            for scope in scopes {
1678                push_hit(scope, ctx);
1679            }
1680        } else if !matched_owner
1681            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1682        {
1683            *ctx.raw_match_count += 1;
1684            push_unproven_hit(scope, ctx);
1685        }
1686        return;
1687    }
1688    let hit_node = node;
1689    let text = node_text(hit_node, ctx.source);
1690    let type_resolution = if hit_node.kind() == "template_type"
1691        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1692    {
1693        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1694            resolve_type_node_lexically_for_target(
1695                hit_node,
1696                &ctx.analyzer,
1697                ctx.visibility,
1698                &ctx.ordinary_type_imports,
1699                ctx.file,
1700                ctx.source,
1701                &ctx.spec.target,
1702                Some(&ctx.lexical_scope_cache),
1703                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1704            )
1705        })
1706    } else {
1707        resolve_type_node_lexically_for_target(
1708            hit_node,
1709            &ctx.analyzer,
1710            ctx.visibility,
1711            &ctx.ordinary_type_imports,
1712            ctx.file,
1713            ctx.source,
1714            &ctx.spec.target,
1715            Some(&ctx.lexical_scope_cache),
1716            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1717        )
1718    };
1719    match type_resolution {
1720        LexicalTypeResolution::Resolved {
1721            unit, candidates, ..
1722        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1723            *ctx.raw_match_count += 1;
1724            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1725                for scope in scopes {
1726                    push_type_hit(scope, ctx);
1727                }
1728            } else {
1729                let hit_node = if recovered_type && hit_node.kind() == "template_type" {
1730                    hit_node
1731                        .child_by_field_name("name")
1732                        .filter(|name| name.kind() == "type_identifier")
1733                        .unwrap_or(hit_node)
1734                } else if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1735                    hit_node
1736                        .child_by_field_name("name")
1737                        .filter(|name| name.kind() == "type_identifier")
1738                        .unwrap_or(hit_node)
1739                } else if ctx
1740                    .analyzer
1741                    .type_alias_provider()
1742                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1743                    && ctx
1744                        .analyzer
1745                        .parent_of(&ctx.spec.target)
1746                        .is_some_and(|owner| owner.is_class())
1747                    && ctx
1748                        .visibility
1749                        .is_exhaustive_same_fqn_type_declaration_family(
1750                            &ctx.analyzer,
1751                            ctx.file,
1752                            &ctx.spec.target,
1753                        )
1754                    && matches!(
1755                        hit_node.kind(),
1756                        "qualified_identifier" | "scoped_type_identifier"
1757                    )
1758                {
1759                    hit_node.child_by_field_name("name").unwrap_or(hit_node)
1760                } else if cpp_template_reference_arguments(hit_node, ctx.source).is_some()
1761                    && ctx.analyzer.type_alias_provider().is_some_and(|provider| {
1762                        candidates.iter().any(|candidate| {
1763                            provider.is_type_alias(candidate)
1764                                && ctx
1765                                    .visibility
1766                                    .is_exhaustive_same_fqn_type_declaration_family(
1767                                        &ctx.analyzer,
1768                                        ctx.file,
1769                                        candidate,
1770                                    )
1771                        })
1772                    })
1773                {
1774                    let terminal = template_reference_name_node(hit_node)
1775                        .map(function_terminal_node)
1776                        .unwrap_or(hit_node);
1777                    push_type_hit(hit_node, ctx);
1778                    if terminal.start_byte() != hit_node.start_byte()
1779                        || terminal.end_byte() != hit_node.end_byte()
1780                    {
1781                        push_type_hit(terminal, ctx);
1782                    }
1783                    return;
1784                } else if qualified_type_scope_contains_template(hit_node) {
1785                    function_terminal_node(hit_node)
1786                } else {
1787                    hit_node
1788                };
1789                let hit_node = type_reference_hit_node(hit_node);
1790                let qualified_alias = qualified_alias_reference_preserves_target(
1791                    node,
1792                    &ctx.spec.target,
1793                    &ctx.analyzer,
1794                    ctx.visibility,
1795                    ctx.file,
1796                    ctx.source,
1797                );
1798                push_type_hit(hit_node, ctx);
1799                if qualified_alias_reference_requires_terminal(qualified_alias)
1800                    || initialized_type_declaration_with_cast(node)
1801                {
1802                    let terminal = function_terminal_node(hit_node);
1803                    if terminal.start_byte() != hit_node.start_byte()
1804                        || terminal.end_byte() != hit_node.end_byte()
1805                    {
1806                        push_type_hit(terminal, ctx);
1807                    }
1808                }
1809            }
1810            return;
1811        }
1812        LexicalTypeResolution::Resolved {
1813            unit: _,
1814            candidates,
1815            ..
1816        } => {
1817            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1818                *ctx.raw_match_count += 1;
1819                for scope in scopes {
1820                    push_type_hit(scope, ctx);
1821                }
1822            } else if let Some(hit) =
1823                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1824            {
1825                *ctx.raw_match_count += 1;
1826                push_unproven_hit(hit, ctx);
1827            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1828                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1829            {
1830                *ctx.raw_match_count += 1;
1831                push_type_hit(leaf, ctx);
1832            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1833                *ctx.raw_match_count += 1;
1834                push_unproven_hit(leaf, ctx);
1835            }
1836            return;
1837        }
1838        LexicalTypeResolution::Ambiguous => {
1839            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1840                *ctx.raw_match_count += 1;
1841                for scope in scopes {
1842                    push_type_hit(scope, ctx);
1843                }
1844            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1845                *ctx.raw_match_count += 1;
1846                push_unproven_hit(leaf, ctx);
1847            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1848                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1849                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1850            {
1851                *ctx.raw_match_count += 1;
1852                push_type_hit(leaf, ctx);
1853            }
1854            return;
1855        }
1856        LexicalTypeResolution::Missing => {
1857            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1858                &ctx.analyzer,
1859                ctx.file,
1860                hit_node,
1861                ctx.source,
1862            ) && same_visible_symbol(&unit, &ctx.spec.target)
1863            {
1864                *ctx.raw_match_count += 1;
1865                push_type_hit(hit_node, ctx);
1866                return;
1867            }
1868            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1869                *ctx.raw_match_count += 1;
1870                push_unproven_hit(leaf, ctx);
1871                return;
1872            }
1873            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1874                *ctx.raw_match_count += 1;
1875                push_unproven_hit(leaf, ctx);
1876                return;
1877            }
1878            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1879                *ctx.raw_match_count += 1;
1880                push_type_hit(leaf, ctx);
1881                return;
1882            }
1883            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1884                hit_node,
1885                &ctx.analyzer,
1886                ctx.visibility,
1887                ctx.file,
1888                ctx.source,
1889                &ctx.spec.target,
1890            );
1891            let raw_matches = matches!(
1892                raw_resolution,
1893                LexicalTypeResolution::Resolved {
1894                    ref unit,
1895                    ref candidates,
1896                    ..
1897                } if type_resolution_identifies_unit_target(
1898                    hit_node,
1899                    unit,
1900                    candidates,
1901                    &ctx.spec.target,
1902                    ctx,
1903                )
1904            );
1905            if raw_matches
1906                || type_node_has_exact_target_identity_without_visibility(
1907                    hit_node,
1908                    &ctx.analyzer,
1909                    ctx.visibility,
1910                    ctx.file,
1911                    ctx.source,
1912                    &ctx.spec.target,
1913                )
1914            {
1915                *ctx.raw_match_count += 1;
1916                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1917                return;
1918            }
1919        }
1920    }
1921    // A class-owned alias can be qualified by an owner made visible through a
1922    // namespace import, including an import in an earlier reopening of the
1923    // same unnamed namespace. Resolve that owner before requiring an
1924    // enclosing class for inherited nested-type lookup.
1925    if ctx
1926        .visibility
1927        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1928    {
1929        *ctx.raw_match_count += 1;
1930        push_type_hit(type_reference_hit_node(hit_node), ctx);
1931        return;
1932    }
1933    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1934        *ctx.raw_match_count += 1;
1935        for scope in scopes {
1936            push_type_hit(scope, ctx);
1937        }
1938        return;
1939    }
1940    if !name_mentions(text, &ctx.spec.member_name) {
1941        return;
1942    }
1943    *ctx.raw_match_count += 1;
1944    if !ctx.visibility.external_type_candidate_visible_in_context(
1945        &ctx.analyzer,
1946        ctx.file,
1947        &ctx.spec.target,
1948        hit_node,
1949    ) {
1950        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1951        if type_reference_resolves_away_from_target(unproven, ctx) {
1952            return;
1953        }
1954        push_unproven_hit(unproven, ctx);
1955    }
1956}
1957
1958fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1959    for reference in object_macro_replacement_type_references(node, ctx.source) {
1960        for component_count in 1..=reference.components.len() {
1961            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1962                node,
1963                &reference.components[..component_count],
1964                reference.global,
1965                &ctx.analyzer,
1966                ctx.visibility,
1967                &ctx.ordinary_type_imports,
1968                ctx.file,
1969                ctx.source,
1970                &ctx.spec.target,
1971                false,
1972                Some(&ctx.lexical_scope_cache),
1973            );
1974            let matches_target = match resolution {
1975                LexicalTypeResolution::Resolved {
1976                    unit, candidates, ..
1977                } => {
1978                    same_visible_symbol(&unit, &ctx.spec.target)
1979                        || candidates
1980                            .iter()
1981                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1982                }
1983                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1984                    &ctx.analyzer,
1985                    ctx.visibility,
1986                    ctx.file,
1987                    &reference.components[..component_count],
1988                )
1989                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1990                LexicalTypeResolution::Ambiguous => false,
1991            };
1992            if !matches_target {
1993                continue;
1994            }
1995            let range = &reference.component_ranges[component_count - 1];
1996            *ctx.raw_match_count += 1;
1997            push_type_hit_range(node, range.start, range.end, ctx);
1998        }
1999    }
2000}
2001
2002fn target_guided_compatible_foreign_import_type_leaf<'tree>(
2003    node: Node<'tree>,
2004    ctx: &ScanCtx<'_>,
2005) -> Option<Node<'tree>> {
2006    let (components, global) = type_reference_components(node, ctx.source)?;
2007    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
2008        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
2009            node,
2010            &ctx.analyzer,
2011            ctx.visibility,
2012            ctx.file,
2013            ctx.source,
2014            false,
2015            false,
2016            Some(&ctx.lexical_scope_cache),
2017        ) {
2018            LexicalScopeResolution::Resolved(scope) => Some(scope),
2019            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
2020        }
2021    })?;
2022    let OrdinaryTypeImportResolution::Resolved { target, .. } =
2023        compatible_foreign_type_import_resolution(
2024            node,
2025            &components,
2026            global,
2027            &ctx.analyzer,
2028            ctx.visibility,
2029            &ctx.ordinary_type_imports,
2030            ctx.file,
2031            ctx.source,
2032            &lexical_scope,
2033            Some(&ctx.spec.target),
2034        )
2035    else {
2036        return None;
2037    };
2038    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2039}
2040
2041/// Resolve the terminal of `Owner::Nested` when complete type lookup cannot
2042/// express the owner as its first qualified component. This occurs when a
2043/// using-directive imports `Owner` and when inherited-type lookup injects it.
2044/// Resolve the owner prefix independently, then retain the terminal after that
2045/// owner proves the target's direct nested declaration.
2046fn target_guided_nested_type_terminal_hit<'tree>(
2047    node: Node<'tree>,
2048    ctx: &ScanCtx<'_>,
2049) -> Option<Node<'tree>> {
2050    // Returns a node of the caller's tree lifetime, which the context-scoped
2051    // ancestry index cannot name; this is one question per call.
2052    let qualified = node.parent().filter(|parent| {
2053        matches!(
2054            parent.kind(),
2055            "qualified_identifier" | "scoped_type_identifier"
2056        ) && parent.child_by_field_name("name") == Some(node)
2057    })?;
2058    let mut complete = qualified;
2059    while let Some(parent) = complete.parent().filter(|parent| {
2060        matches!(
2061            parent.kind(),
2062            "qualified_identifier" | "scoped_type_identifier"
2063        )
2064    }) {
2065        complete = parent;
2066    }
2067    let owner = qualified_owner_components(complete, ctx.source)?;
2068
2069    if let LexicalTypeResolution::Resolved {
2070        unit, candidates, ..
2071    } = resolve_type_node_lexically_for_target(
2072        complete,
2073        &ctx.analyzer,
2074        ctx.visibility,
2075        &ctx.ordinary_type_imports,
2076        ctx.file,
2077        ctx.source,
2078        &ctx.spec.target,
2079        Some(&ctx.lexical_scope_cache),
2080        ctx.recovered_sentinel_scope(complete).as_deref(),
2081    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2082        && ctx
2083            .analyzer
2084            .parent_of(&ctx.spec.target)
2085            .is_some_and(|parent| parent.is_class())
2086        && !ctx
2087            .analyzer
2088            .type_alias_provider()
2089            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2090        && is_namespace_scope_type_reference(complete)
2091        && namespace_import_can_name_nested_target(complete, ctx)
2092    {
2093        return Some(node);
2094    }
2095
2096    let target_owner = ctx
2097        .analyzer
2098        .parent_of(&ctx.spec.target)
2099        .filter(CodeUnit::is_class);
2100    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2101        let target_components = canonical_cpp_scope_components(target_owner);
2102        if owner.global {
2103            target_components == owner.names
2104        } else {
2105            target_components.ends_with(&owner.names)
2106        }
2107    });
2108    let target_member_visible = |target_owner: &CodeUnit| {
2109        ctx.visibility
2110            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2111            .into_iter()
2112            .any(|candidate| {
2113                same_visible_symbol(candidate, &ctx.spec.target)
2114                    && (ctx
2115                        .visibility
2116                        .external_type_candidate_guard_compatible_in_context(
2117                            &ctx.analyzer,
2118                            ctx.file,
2119                            candidate,
2120                            complete,
2121                        )
2122                        || (ctx
2123                            .visibility
2124                            .is_exhaustive_same_fqn_type_declaration_family(
2125                                &ctx.analyzer,
2126                                ctx.file,
2127                                candidate,
2128                            )
2129                            && ctx.visibility.external_type_candidate_visible_in_context(
2130                                &ctx.analyzer,
2131                                ctx.file,
2132                                candidate,
2133                                complete,
2134                            )))
2135            })
2136    };
2137
2138    if owner_spelling_can_name_target
2139        && (is_namespace_scope_type_reference(complete)
2140            || (is_compound_type_reference(complete)
2141                && namespace_import_can_name_nested_target(complete, ctx)))
2142        && let Some(target_owner) = target_owner.as_ref()
2143        && let LexicalTypeResolution::Resolved { unit, .. } =
2144            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2145                complete,
2146                &owner.names,
2147                owner.global,
2148                &ctx.analyzer,
2149                ctx.visibility,
2150                &ctx.ordinary_type_imports,
2151                ctx.file,
2152                ctx.source,
2153                Some(&ctx.lexical_scope_cache),
2154            )
2155        && ctx
2156            .visibility
2157            .same_template_owner_identity(&unit, target_owner)
2158        && target_member_visible(target_owner)
2159    {
2160        return Some(node);
2161    }
2162
2163    if ctx
2164        .analyzer
2165        .type_alias_provider()
2166        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2167        && !ctx
2168            .analyzer
2169            .parent_of(&ctx.spec.target)
2170            .is_some_and(|owner| owner.is_class())
2171        && let Some(target_owner) = target_owner.as_ref()
2172        && let LexicalTypeResolution::Resolved {
2173            unit, candidates, ..
2174        } = resolve_type_components_lexically_at_for_target_with_scope_cache(
2175            complete,
2176            &owner.names,
2177            owner.global,
2178            &ctx.analyzer,
2179            ctx.visibility,
2180            &ctx.ordinary_type_imports,
2181            ctx.file,
2182            ctx.source,
2183            target_owner,
2184            false,
2185            Some(&ctx.lexical_scope_cache),
2186        )
2187        && (ctx
2188            .visibility
2189            .same_template_owner_identity(&unit, target_owner)
2190            || candidates.iter().any(|candidate| {
2191                ctx.visibility
2192                    .same_template_owner_identity(candidate, target_owner)
2193            }))
2194        && target_member_visible(target_owner)
2195    {
2196        return Some(node);
2197    }
2198
2199    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2200    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2201    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2202        &ctx.analyzer,
2203        ctx.file,
2204        &owner.names,
2205        owner.global,
2206        &lexical_scope,
2207    );
2208    let owner_unit = match owner_resolution {
2209        LexicalTypeResolution::Resolved { unit, .. } => unit,
2210        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2211            ctx.visibility.inherited_injected_class_owner(
2212                &ctx.analyzer,
2213                ctx.file,
2214                &enclosing_owner,
2215                owner.names.first()?,
2216            )?
2217        }
2218        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2219    };
2220    let name = node_text(node, ctx.source);
2221    ctx.visibility
2222        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2223        .into_iter()
2224        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2225        .then_some(complete)
2226}
2227
2228fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2229    // Namespace-scope nested classes need a terminal edge when the complete
2230    // qualified node is the only structured evidence (as in #2213). In
2231    // parameters, fields, and callable bodies the enclosing type node already
2232    // owns the exact range; adding its terminal would duplicate that edge.
2233    let mut current = node.parent();
2234    while let Some(parent) = current {
2235        match parent.kind() {
2236            "call_expression" | "new_expression" => return true,
2237            "parameter_declaration"
2238            | "optional_parameter_declaration"
2239            | "field_declaration"
2240            | "function_definition"
2241            | "function_declarator"
2242            | "class_specifier"
2243            | "struct_specifier"
2244            | "union_specifier"
2245            | "compound_statement" => return false,
2246            "declaration" => {
2247                let mut cursor = parent.walk();
2248                if parent
2249                    .named_children(&mut cursor)
2250                    .any(|child| child.kind() == "function_declarator")
2251                {
2252                    return false;
2253                }
2254            }
2255            _ => {}
2256        }
2257        current = parent.parent();
2258    }
2259    true
2260}
2261
2262fn is_compound_type_reference(node: Node<'_>) -> bool {
2263    let mut current = node.parent();
2264    while let Some(parent) = current {
2265        match parent.kind() {
2266            "compound_statement" => return true,
2267            "namespace_definition" | "translation_unit" => return false,
2268            _ => current = parent.parent(),
2269        }
2270    }
2271    false
2272}
2273
2274fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2275    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2276        return false;
2277    };
2278    effective_using_bindings_for_name(
2279        ctx.visibility,
2280        &ctx.ordinary_type_imports,
2281        ctx.file,
2282        node,
2283        ctx.source,
2284        target_owner.identifier(),
2285    )
2286    .iter()
2287    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2288}
2289
2290/// Recover the class-template qualifier in a dependent leading return type of
2291/// an out-of-line member definition.
2292///
2293/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2294/// `Owner`: one in the return type and one in the callable declarator. The
2295/// latter already has authoritative indexed owner resolution. Reuse that
2296/// structured owner evidence for the former when both template names agree;
2297/// resolving `Owner<T>::Alias` as one type would instead select terminal
2298/// `Alias` and discard the qualifier.
2299fn out_of_line_dependent_return_template_owner<'tree>(
2300    node: Node<'tree>,
2301    ctx: &ScanCtx<'_>,
2302) -> Option<Node<'tree>> {
2303    if node.kind() != "template_type" {
2304        return None;
2305    }
2306    let template_name = template_reference_name_node(node)?;
2307    let mut scope = node;
2308    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2309        matches!(
2310            parent.kind(),
2311            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2312        ) && parent.child_by_field_name("name") == Some(scope)
2313    }) {
2314        scope = parent;
2315    }
2316    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2317        matches!(
2318            parent.kind(),
2319            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2320        ) && parent.child_by_field_name("scope") == Some(scope)
2321    })?;
2322    let mut function = ctx.ancestry.parent(qualified);
2323    let function = loop {
2324        let candidate = function?;
2325        if candidate.kind() == "function_definition" {
2326            break candidate;
2327        }
2328        function = ctx.ancestry.parent(candidate);
2329    };
2330    let return_type = function.child_by_field_name("type")?;
2331    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2332        return None;
2333    }
2334    let declarator_name = function
2335        .child_by_field_name("declarator")
2336        .and_then(declarator_name_node)?;
2337    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2338        return None;
2339    }
2340    let resolved_owner_matches = out_of_line_member_definition_owner(
2341        &ctx.analyzer,
2342        ctx.visibility,
2343        ctx.file,
2344        ctx.source,
2345        declarator_name,
2346    )
2347    .is_some_and(|owners| {
2348        owners
2349            .owners
2350            .iter()
2351            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2352    });
2353    let indexed_owner_matches =
2354        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2355    let target_guided_owner_matches =
2356        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2357    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2358        .then_some(template_name)
2359}
2360
2361/// Use the indexed callable identity when a malformed namespace sentinel
2362/// prevents lexical lookup of an out-of-line template owner.
2363fn indexed_out_of_line_template_owner_hit<'tree>(
2364    node: Node<'tree>,
2365    ctx: &ScanCtx<'_>,
2366) -> Option<Node<'tree>> {
2367    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2368        || !is_declaration_name(node)
2369    {
2370        return None;
2371    }
2372    let mut function = ctx.ancestry.parent(node);
2373    let function = loop {
2374        let candidate = function?;
2375        if candidate.kind() == "function_definition" {
2376            break candidate;
2377        }
2378        function = ctx.ancestry.parent(candidate);
2379    };
2380    if function
2381        .child_by_field_name("declarator")
2382        .and_then(declarator_name_node)
2383        != Some(node)
2384    {
2385        return None;
2386    }
2387    let target = physically_visible_type_target(ctx)?;
2388    let qualified = qualified_owner_components(node, ctx.source)?;
2389    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2390        return None;
2391    }
2392    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2393        != canonical_cpp_scope_components(target)
2394    {
2395        return None;
2396    }
2397    let owner = qualified.nodes.last().copied()?;
2398    let template = if owner.kind() == "template_type" {
2399        owner
2400    } else {
2401        owner
2402            .parent()
2403            .filter(|parent| parent.kind() == "template_type")?
2404    };
2405    template_reference_name_node(template)
2406}
2407
2408fn push_guarded_owner_hit(
2409    owner_node: Node<'_>,
2410    owner: &CodeUnit,
2411    reference: Node<'_>,
2412    ctx: &mut ScanCtx<'_>,
2413) {
2414    if ctx
2415        .visibility
2416        .external_type_candidate_guard_compatible_in_context(
2417            &ctx.analyzer,
2418            ctx.file,
2419            owner,
2420            reference,
2421        )
2422    {
2423        push_hit(owner_node, ctx);
2424    } else {
2425        push_unproven_hit(owner_node, ctx);
2426    }
2427}
2428
2429/// Preserve a direct template-alias reference when a macro namespace sentinel
2430/// makes tree-sitter drop the first source path component.
2431fn target_guided_alias_template_reference<'tree>(
2432    node: Node<'tree>,
2433    ctx: &ScanCtx<'_>,
2434) -> Option<(Node<'tree>, bool)> {
2435    let alias_provider = ctx.analyzer.type_alias_provider()?;
2436    if !matches!(
2437        node.kind(),
2438        "qualified_identifier" | "scoped_type_identifier"
2439    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2440    {
2441        return None;
2442    }
2443    cpp_template_reference_arguments(node, ctx.source)?;
2444    let (components, global) = type_reference_components(node, ctx.source)?;
2445    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2446        return None;
2447    }
2448    let target = physically_visible_type_target(ctx)?;
2449    let target_components = canonical_cpp_scope_components(target);
2450    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2451    let path_matches = if global {
2452        components == target_components
2453            || (!parser_namespace.is_empty()
2454                && target_components.starts_with(&parser_namespace)
2455                && target_components[parser_namespace.len()..] == components)
2456    } else {
2457        let lexical_scope = match enclosing_lexical_scope_components(
2458            node,
2459            &ctx.analyzer,
2460            ctx.visibility,
2461            ctx.file,
2462            ctx.source,
2463        ) {
2464            LexicalScopeResolution::Resolved(scope) => scope,
2465            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2466        };
2467        lexical_component_tiers(&components, false, &lexical_scope)
2468            .any(|scope| scope == target_components)
2469    };
2470    let scoped_candidates = ctx
2471        .visibility
2472        .visible_identifier_candidates(ctx.file, target.identifier())
2473        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2474        .collect::<Vec<_>>();
2475    if !path_matches
2476        || scoped_candidates.is_empty()
2477        || scoped_candidates
2478            .iter()
2479            .any(|candidate| !same_visible_symbol(candidate, target))
2480        || !ctx.visibility.structured_alias_primary_preserves_target(
2481            &ctx.analyzer,
2482            ctx.file,
2483            target,
2484            target,
2485        )
2486    {
2487        return None;
2488    }
2489    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2490        &ctx.analyzer,
2491        ctx.file,
2492        target,
2493        node,
2494    );
2495    Some((node, proven))
2496}
2497
2498/// Tree-sitter can split a macro-qualified member return type into a phantom
2499/// field followed by the real function definition.  The declaration visitor
2500/// discards that phantom field, but its declarator token remains a semantic
2501/// type reference. Resolve it from the recovered or indexed class scope so an
2502/// enclosing-class alias remains distinguishable from same-spelled siblings.
2503fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2504    let name = node_text(return_type, ctx.source);
2505    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2506        return;
2507    }
2508    if physically_visible_type_target(ctx).is_some()
2509        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2510        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2511        && ctx.visibility.external_type_candidate_visible_in_context(
2512            &ctx.analyzer,
2513            ctx.file,
2514            &ctx.spec.target,
2515            return_type,
2516        )
2517    {
2518        *ctx.raw_match_count += 1;
2519        push_type_hit(return_type, ctx);
2520        return;
2521    }
2522    let Some(scope) = ctx
2523        .recovered_sentinel_scope(return_type)
2524        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2525    else {
2526        return;
2527    };
2528    let components = [name.to_string()];
2529    let resolution = ctx.visibility.resolve_type_components_lexically(
2530        &ctx.analyzer,
2531        ctx.file,
2532        &components,
2533        false,
2534        &scope,
2535    );
2536    if let LexicalTypeResolution::Resolved {
2537        unit, candidates, ..
2538    } = resolution
2539        && (same_visible_symbol(&unit, &ctx.spec.target)
2540            || candidates
2541                .iter()
2542                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2543    {
2544        *ctx.raw_match_count += 1;
2545        push_type_hit(return_type, ctx);
2546    }
2547}
2548
2549/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2550/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2551/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2552/// than a type node; ordinary type-reference traversal therefore skips it.
2553fn member_pointer_owner_components<'tree>(
2554    node: Node<'tree>,
2555    source: &str,
2556) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2557    if node.kind() != "qualified_identifier" {
2558        return None;
2559    }
2560    let declarator = node.child_by_field_name("name")?;
2561    if !matches!(
2562        declarator.kind(),
2563        "pointer_type_declarator" | "abstract_pointer_declarator"
2564    ) {
2565        return None;
2566    }
2567    let scope = node.child_by_field_name("scope")?;
2568    // Keep this check entirely structural.  C and C++ share the
2569    // `qualified_identifier` node shape for some recovered declarations, but
2570    // only the C++ member-pointer form has an actual `::` grammar child
2571    // between the owner scope and pointer declarator.  Looking at the source
2572    // slice here would make a recovered C declarator look like a C++ owner
2573    // merely because its bytes happen to contain the same punctuation.
2574    let mut saw_scope = false;
2575    let has_scope_separator = (0..node.child_count()).any(|index| {
2576        let Some(child) = node.child(index) else {
2577            return false;
2578        };
2579        if same_node(child, scope) {
2580            saw_scope = true;
2581            return false;
2582        }
2583        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2584    });
2585    if !has_scope_separator {
2586        return None;
2587    }
2588    let mut nodes = cpp_name_component_nodes(scope)?;
2589    let mut outer = node;
2590    while let Some(parent) = outer.parent()
2591        && parent.kind() == "qualified_identifier"
2592        && parent.child_by_field_name("name") == Some(outer)
2593    {
2594        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2595        prefix.append(&mut nodes);
2596        nodes = prefix;
2597        outer = parent;
2598    }
2599    let names = nodes
2600        .iter()
2601        .map(|component| node_text(*component, source).to_string())
2602        .collect();
2603    Some((
2604        QualifiedOwnerComponents {
2605            nodes,
2606            names,
2607            global: is_globally_qualified_cpp_name(outer),
2608        },
2609        outer,
2610    ))
2611}
2612
2613fn member_pointer_alias_owner_prefix_matches(
2614    node: Node<'_>,
2615    owner: &QualifiedOwnerComponents<'_>,
2616    ctx: &ScanCtx<'_>,
2617) -> bool {
2618    let Some((_, owner_prefix)) = owner.names.split_last() else {
2619        return false;
2620    };
2621    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2622        return false;
2623    };
2624    let recovered_scope = ctx.recovered_sentinel_scope(node);
2625    let resolution = if let Some(recovered_scope) = recovered_scope {
2626        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2627            node,
2628            owner_prefix,
2629            owner.global,
2630            &ctx.analyzer,
2631            ctx.visibility,
2632            &ctx.ordinary_type_imports,
2633            ctx.file,
2634            ctx.source,
2635            &parent,
2636            false,
2637            &recovered_scope,
2638        )
2639    } else {
2640        resolve_type_components_lexically_at_for_target_with_scope_cache(
2641            node,
2642            owner_prefix,
2643            owner.global,
2644            &ctx.analyzer,
2645            ctx.visibility,
2646            &ctx.ordinary_type_imports,
2647            ctx.file,
2648            ctx.source,
2649            &parent,
2650            false,
2651            Some(&ctx.lexical_scope_cache),
2652        )
2653    };
2654    let LexicalTypeResolution::Resolved {
2655        unit, candidates, ..
2656    } = resolution
2657    else {
2658        return false;
2659    };
2660    same_member_pointer_owner_identity(&unit, &parent)
2661        || candidates
2662            .iter()
2663            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2664}
2665
2666fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2667    same_visible_symbol(left, right)
2668        || (left.kind() == right.kind()
2669            && left.fq_name() == right.fq_name()
2670            && left.source() == right.source())
2671}
2672
2673/// Resolve a template type nested in a qualified identifier against a concrete
2674/// type target. The target-guided lexical path intentionally applies a
2675/// structured candidate prefilter; that prefilter cannot see a partial
2676/// specialization until the template arguments have selected it. Resolve the
2677/// complete qualified primary first, then apply the parsed arguments and
2678/// retain the result only when it is the requested target.
2679fn resolve_nested_template_type_for_target(
2680    node: Node<'_>,
2681    ctx: &ScanCtx<'_>,
2682) -> Option<LexicalTypeResolution> {
2683    let reference_node = node
2684        .parent()
2685        .filter(|parent| {
2686            parent.kind() == "qualified_identifier"
2687                && parent.child_by_field_name("name") == Some(node)
2688        })
2689        .unwrap_or(node);
2690    let target_resolution = resolve_type_node_lexically_for_target(
2691        reference_node,
2692        &ctx.analyzer,
2693        ctx.visibility,
2694        &ctx.ordinary_type_imports,
2695        ctx.file,
2696        ctx.source,
2697        &ctx.spec.target,
2698        Some(&ctx.lexical_scope_cache),
2699        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2700    );
2701    if let LexicalTypeResolution::Resolved {
2702        unit, candidates, ..
2703    } = target_resolution
2704        && template_reference_candidates_select_target(
2705            reference_node,
2706            &candidates,
2707            &ctx.analyzer,
2708            ctx.visibility,
2709            ctx.file,
2710            ctx.source,
2711            &ctx.spec.target,
2712        )
2713    {
2714        return Some(LexicalTypeResolution::Resolved {
2715            unit,
2716            components: Vec::new(),
2717            candidates,
2718        });
2719    }
2720
2721    let normal_resolution = resolve_type_node_lexically(
2722        reference_node,
2723        &ctx.analyzer,
2724        ctx.visibility,
2725        &ctx.ordinary_type_imports,
2726        ctx.file,
2727        ctx.source,
2728    );
2729    let LexicalTypeResolution::Resolved {
2730        unit,
2731        components,
2732        candidates,
2733    } = normal_resolution
2734    else {
2735        return None;
2736    };
2737    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2738    let specialized = ctx
2739        .visibility
2740        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2741        .ok()
2742        .unwrap_or(unit);
2743    (same_visible_symbol(&specialized, &ctx.spec.target)
2744        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2745    .then_some(LexicalTypeResolution::Resolved {
2746        unit: specialized,
2747        components,
2748        candidates,
2749    })
2750}
2751
2752fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2753    let mut reference = node;
2754    while let Some(parent) = ctx.ancestry.parent(reference) {
2755        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2756            && parent.child_by_field_name("name") == Some(reference)
2757            || matches!(
2758                parent.kind(),
2759                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2760            ) && (parent.child_by_field_name("scope") == Some(reference)
2761                || parent.child_by_field_name("name") == Some(reference));
2762        if !owns_component {
2763            break;
2764        }
2765        reference = parent;
2766    }
2767    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2768        return false;
2769    };
2770    type_reference_component_list_may_name_target(
2771        &components,
2772        global,
2773        &ctx.analyzer,
2774        ctx.visibility,
2775        ctx.file,
2776        &ctx.spec.target,
2777        &ctx.type_reference_component_names,
2778    )
2779}
2780
2781fn type_reference_component_list_may_name_target(
2782    components: &[String],
2783    global: bool,
2784    analyzer: &CppGraphSource<'_>,
2785    visibility: &VisibilityIndex<'_>,
2786    file: &ProjectFile,
2787    target: &CodeUnit,
2788    reference_component_names: &HashSet<String>,
2789) -> bool {
2790    if components.iter().any(|component| {
2791        visibility.type_reference_component_directly_names_target(component, target)
2792    }) {
2793        return true;
2794    }
2795    if components.iter().any(|component| {
2796        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2797    }) {
2798        return true;
2799    }
2800    if components.len() > 1 {
2801        return visibility.qualified_alias_reference_may_reach_target(
2802            analyzer, file, components, global, target,
2803        );
2804    }
2805    components
2806        .iter()
2807        .any(|component| reference_component_names.contains(component))
2808}
2809
2810/// Fast structured admission before a type scan builds its parent and lexical
2811/// indexes. Most inverse candidates merely include the target's header and do
2812/// not contain a reference spelling; the full stateful scan has no work in
2813/// those files. Only maximal grammar type names are tested so a common nested
2814/// alias terminal such as `type` is considered together with its owner.
2815fn file_may_reference_type_target(
2816    root: Node<'_>,
2817    source: &str,
2818    analyzer: &CppGraphSource<'_>,
2819    visibility: &VisibilityIndex<'_>,
2820    file: &ProjectFile,
2821    target: &CodeUnit,
2822    reference_component_names: &HashSet<String>,
2823) -> bool {
2824    visit_file_type_reference_spellings(root, source, |components, global| {
2825        type_reference_component_list_may_name_target(
2826            components,
2827            global,
2828            analyzer,
2829            visibility,
2830            file,
2831            target,
2832            reference_component_names,
2833        )
2834    })
2835}
2836
2837fn visit_file_type_reference_spellings(
2838    root: Node<'_>,
2839    source: &str,
2840    mut visit: impl FnMut(&[String], bool) -> bool,
2841) -> bool {
2842    let mut stopped = false;
2843    walk_named_tree_preorder(root, true, |node| {
2844        if node.kind() == "comment" {
2845            return WalkControl::SkipChildren;
2846        }
2847        if node.kind() == "preproc_arg" {
2848            stopped = object_macro_replacement_type_references(node, source)
2849                .into_iter()
2850                .any(|reference| visit(&reference.components, reference.global));
2851            return if stopped {
2852                WalkControl::Break
2853            } else {
2854                WalkControl::SkipChildren
2855            };
2856        }
2857        if node.kind() == "field_declaration"
2858            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2859        {
2860            let components = [node_text(return_type, source).to_string()];
2861            stopped = visit(&components, false);
2862            if stopped {
2863                return WalkControl::Break;
2864            }
2865        }
2866        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2867            for candidate in [node, type_node] {
2868                let Some((components, global)) = type_reference_components(candidate, source)
2869                else {
2870                    continue;
2871                };
2872                stopped = visit(&components, global);
2873                if stopped {
2874                    return WalkControl::Break;
2875                }
2876            }
2877        }
2878        let Some((components, global)) = type_reference_components(node, source) else {
2879            return WalkControl::Continue;
2880        };
2881        if node.parent().is_some_and(|parent| {
2882            (matches!(parent.kind(), "template_type" | "template_function")
2883                && parent.child_by_field_name("name") == Some(node))
2884                || (matches!(
2885                    parent.kind(),
2886                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2887                ) && (parent.child_by_field_name("scope") == Some(node)
2888                    || parent.child_by_field_name("name") == Some(node)))
2889        }) {
2890            return WalkControl::Continue;
2891        }
2892        stopped = visit(&components, global);
2893        if stopped {
2894            WalkControl::Break
2895        } else {
2896            WalkControl::Continue
2897        }
2898    });
2899    stopped
2900}
2901
2902/// Whether this file decides the reference against the scan target.
2903///
2904/// The last resort of type admission has no structured resolution left: it
2905/// only knows that the spelling mentions the target's name and that the target
2906/// itself is not visible here. That is undecidable when nothing else declares
2907/// the name, but it is a proven negative when the file already makes another
2908/// declaration of the same name visible, because unqualified lookup binds the
2909/// reference to that declaration and the include closure never reaches the
2910/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2911/// the same question before it resolves; asking it again here keeps the
2912/// inverse from admitting a site that forward resolution has already decided
2913/// (#2916).
2914fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2915    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2916        return false;
2917    };
2918    let terminal = components
2919        .last()
2920        .expect("type reference components are non-empty");
2921    if terminal != ctx.spec.target.identifier() {
2922        return false;
2923    }
2924    // A concrete specialization is selected from the template arguments, not
2925    // from the visible primary name, so the name-level candidate set cannot
2926    // decide it. `resolve_type_node_lexically_for_target` makes the same
2927    // exception before its own structured prefilter.
2928    if cpp_template_reference_arguments(node, ctx.source).is_some()
2929        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2930    {
2931        return false;
2932    }
2933    // One C++ type reached through a forward declaration in one header and its
2934    // definition in another is two CodeUnits with different signatures and one
2935    // qualified name. A file that sees either of them sees the target's type,
2936    // so only a candidate under a different qualified name decides the
2937    // reference against it. This is what separates the decoy in an unrelated
2938    // namespace from a redeclaration of the target itself.
2939    if ctx
2940        .visibility
2941        .visible_identifier_candidates(ctx.file, terminal)
2942        .any(|candidate| {
2943            candidate.kind() == ctx.spec.target.kind()
2944                && candidate.fq_name() == ctx.spec.target.fq_name()
2945        })
2946    {
2947        return false;
2948    }
2949    !ctx.visibility
2950        .structured_type_reference_may_resolve_to_target(
2951            &ctx.analyzer,
2952            ctx.file,
2953            std::slice::from_ref(terminal),
2954            false,
2955            &[],
2956            &ctx.spec.target,
2957        )
2958}
2959
2960fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2961    let Some(scope) = node.child_by_field_name("scope") else {
2962        return false;
2963    };
2964    let mut pending = vec![scope];
2965    while let Some(candidate) = pending.pop() {
2966        if candidate.kind() == "template_type" {
2967            return true;
2968        }
2969        if matches!(
2970            candidate.kind(),
2971            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2972        ) {
2973            if let Some(scope) = candidate.child_by_field_name("scope") {
2974                pending.push(scope);
2975            }
2976            if let Some(name) = candidate.child_by_field_name("name") {
2977                pending.push(name);
2978            }
2979        }
2980    }
2981    false
2982}
2983
2984fn call_for_function_node(node: Node<'_>) -> Option<Node<'_>> {
2985    let parent = node.parent()?;
2986    (parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node))
2987        .then_some(parent)
2988}
2989
2990fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2991    ctx.target_group.iter().find(|target| {
2992        same_logical_symbol(target, &ctx.spec.target)
2993            && ctx.visibility.is_physically_visible(ctx.file, target)
2994    })
2995}
2996
2997fn target_guided_missing_direct_temporary_type<'tree>(
2998    function: Node<'tree>,
2999    ctx: &ScanCtx<'_>,
3000) -> Option<Node<'tree>> {
3001    let target = physically_visible_type_target(ctx)?;
3002    let component_nodes = cpp_name_component_nodes(function)?;
3003    let terminal = component_nodes.last().copied()?;
3004    if node_text(terminal, ctx.source) != target.identifier() {
3005        return None;
3006    }
3007    let components = component_nodes
3008        .iter()
3009        .map(|component| node_text(*component, ctx.source).to_string())
3010        .collect::<Vec<_>>();
3011    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
3012    indexed_scope_matches_target_name(
3013        &indexed_scope,
3014        &components,
3015        is_globally_qualified_cpp_name(function),
3016        target,
3017    )
3018    .then_some(terminal)
3019}
3020
3021fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
3022    let Some(function) = call.child_by_field_name("function") else {
3023        return;
3024    };
3025    if !matches!(
3026        function.kind(),
3027        "identifier"
3028            | "type_identifier"
3029            | "template_function"
3030            | "template_type"
3031            | "qualified_identifier"
3032            | "scoped_identifier"
3033            | "scoped_type_identifier"
3034    ) {
3035        return;
3036    }
3037    if !type_reference_components_may_name_target(function, ctx) {
3038        return;
3039    }
3040    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3041        *ctx.raw_match_count += 1;
3042        for scope in scopes {
3043            push_type_hit(scope, ctx);
3044        }
3045        return;
3046    }
3047    let terminal = function_terminal_node(function);
3048    let name = node_text(terminal, ctx.source);
3049    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3050        return;
3051    }
3052    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3053        match resolve_declaring_member_owner(
3054            &ctx.analyzer,
3055            ctx.visibility,
3056            ctx.file,
3057            &enclosing_owner,
3058            name,
3059        ) {
3060            EnclosingMemberOwnerResolution::Owner(owner)
3061                if matches!(
3062                    ctx.visibility
3063                        .visible_member_for_owner_name(ctx.file, &owner, name,),
3064                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3065                ) =>
3066            {
3067                return;
3068            }
3069            EnclosingMemberOwnerResolution::Ambiguous => return,
3070            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3071        }
3072    }
3073
3074    // A type alias used as a direct temporary (`result_type(value)`) is parsed
3075    // as an ordinary identifier call. Callable lookup can be ambiguous when
3076    // parser recovery flattens a namespace or same-spelled aliases are
3077    // visible from sibling distributions. An exact enclosing class owner
3078    // proves the member alias without treating the call as an arbitrary name;
3079    // retain the shadow and declaration-visibility guards above and below.
3080    if ctx
3081        .analyzer
3082        .type_alias_provider()
3083        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3084        && name == ctx.spec.target.identifier()
3085        && physically_visible_type_target(ctx).is_some()
3086        && !local_type_name_shadows(function, ctx)
3087        && type_alias_owner_matches_structured_reference(function, ctx)
3088        && ctx.visibility.external_type_candidate_visible_in_context(
3089            &ctx.analyzer,
3090            ctx.file,
3091            &ctx.spec.target,
3092            function,
3093        )
3094    {
3095        *ctx.raw_match_count += 1;
3096        push_type_hit(terminal, ctx);
3097        return;
3098    }
3099
3100    let call_resolution = resolve_qualified_call_target(
3101        call,
3102        function,
3103        &ctx.analyzer,
3104        ctx.visibility,
3105        &ctx.ordinary_type_imports,
3106        ctx.file,
3107        ctx.source,
3108    );
3109    match call_resolution {
3110        BareCallTargetResolution::Type(unit) => {
3111            if same_visible_symbol(&unit, &ctx.spec.target) {
3112                *ctx.raw_match_count += 1;
3113                push_type_hit(function, ctx);
3114                return;
3115            }
3116        }
3117        BareCallTargetResolution::FreeFunctions(units)
3118            if units.iter().all(|unit| {
3119                unit.fq_name() == ctx.spec.target.fq_name()
3120                    && ctx
3121                        .visibility
3122                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
3123            }) => {}
3124        BareCallTargetResolution::Ambiguous => {
3125            push_unproven_hit(function, ctx);
3126            return;
3127        }
3128        BareCallTargetResolution::FreeFunctions(_)
3129        | BareCallTargetResolution::UnprovenFreeFunctions(_)
3130        | BareCallTargetResolution::CallableShadow => return,
3131        // Generated `.c` includes can leave ordinary callable resolution with
3132        // no active callable even when target-preserving type resolution can
3133        // prove the constructor's class. Let the structured type fallback
3134        // below make that decision.
3135        BareCallTargetResolution::Missing => {}
3136    }
3137    let target_resolution = resolve_type_node_lexically_for_target(
3138        function,
3139        &ctx.analyzer,
3140        ctx.visibility,
3141        &ctx.ordinary_type_imports,
3142        ctx.file,
3143        ctx.source,
3144        &ctx.spec.target,
3145        Some(&ctx.lexical_scope_cache),
3146        ctx.recovered_sentinel_scope(function).as_deref(),
3147    );
3148    match target_resolution {
3149        LexicalTypeResolution::Resolved {
3150            unit, candidates, ..
3151        } if same_visible_symbol(&unit, &ctx.spec.target)
3152            || candidates
3153                .iter()
3154                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3155        {
3156            *ctx.raw_match_count += 1;
3157            push_type_hit(function, ctx);
3158        }
3159        LexicalTypeResolution::Missing => {
3160            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3161                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3162            {
3163                *ctx.raw_match_count += 1;
3164                push_type_hit(hit, ctx);
3165            }
3166        }
3167        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3168    }
3169}
3170
3171pub enum BareCallTargetResolution {
3172    Type(CodeUnit),
3173    FreeFunctions(Vec<CodeUnit>),
3174    UnprovenFreeFunctions(Vec<CodeUnit>),
3175    CallableShadow,
3176    Ambiguous,
3177    Missing,
3178}
3179
3180pub enum BlockUsingCallTargetResolution {
3181    Target(BareCallTargetResolution),
3182    Unindexed(Vec<String>),
3183    Ambiguous,
3184}
3185
3186#[allow(clippy::too_many_arguments)]
3187fn resolve_qualified_call_target(
3188    call: Node<'_>,
3189    function: Node<'_>,
3190    analyzer: &CppGraphSource<'_>,
3191    visibility: &VisibilityIndex,
3192    ordinary_type_imports: &OrdinaryTypeImportCell,
3193    file: &ProjectFile,
3194    source: &str,
3195) -> BareCallTargetResolution {
3196    if matches!(function.kind(), "identifier" | "template_function") {
3197        return resolve_bare_call_target(
3198            call,
3199            function,
3200            analyzer,
3201            visibility,
3202            ordinary_type_imports,
3203            file,
3204            source,
3205        );
3206    }
3207    if !matches!(
3208        function.kind(),
3209        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3210    ) {
3211        return BareCallTargetResolution::Missing;
3212    }
3213    let terminal = function_terminal_node(function);
3214    let name = node_text(terminal, source);
3215    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3216        return BareCallTargetResolution::Missing;
3217    };
3218    components.push(name.to_string());
3219    let qualified_name = components.join("::");
3220    let type_resolution = resolve_type_node_lexically(
3221        function,
3222        analyzer,
3223        visibility,
3224        ordinary_type_imports,
3225        file,
3226        source,
3227    );
3228    let same_name_resolves_to_type = matches!(
3229        &type_resolution,
3230        LexicalTypeResolution::Resolved {
3231            unit,
3232            candidates,
3233            ..
3234        } if cpp_name_for(unit) == qualified_name
3235            || candidates
3236                .iter()
3237                .any(|candidate| cpp_name_for(candidate) == qualified_name)
3238    );
3239    let has_explicit_template_arguments =
3240        cpp_template_reference_arguments(function, source).is_some();
3241    let candidates = visibility
3242        .visible_identifier_candidates(file, name)
3243        .filter(|candidate| {
3244            candidate.is_function()
3245                && type_owner_of(analyzer, candidate).is_none()
3246                && !(same_name_resolves_to_type
3247                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3248                        || has_explicit_template_arguments
3249                            && visibility
3250                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
3251                && cpp_name_for(candidate) == qualified_name
3252                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3253        })
3254        .cloned()
3255        .collect::<Vec<_>>();
3256    if !candidates.is_empty() {
3257        return resolve_callable_candidates(
3258            candidates,
3259            visibility.call_arity_evidence(file, call, source).exact(),
3260            call.start_byte(),
3261            analyzer,
3262            visibility,
3263            file,
3264        );
3265    }
3266    match type_resolution {
3267        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3268        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3269        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3270    }
3271}
3272
3273fn binding_free_function_candidates(
3274    binding: &OrdinaryTypeImport,
3275    active_bindings: &[&OrdinaryTypeImport],
3276    analyzer: &CppGraphSource<'_>,
3277    visibility: &VisibilityIndex<'_>,
3278    file: &ProjectFile,
3279    name: &str,
3280    reference_byte: usize,
3281) -> Vec<CodeUnit> {
3282    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3283        return Vec::new();
3284    };
3285    let mut targets = Vec::new();
3286    match binding.target {
3287        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3288        EffectiveUsingTarget::Namespace { .. } => {
3289            let mut stack = vec![qualified.clone()];
3290            let mut visited = HashSet::default();
3291            while let Some(namespace) = stack.pop() {
3292                if !visited.insert(namespace.clone()) {
3293                    continue;
3294                }
3295                let mut target = namespace.clone();
3296                target.push(name.to_string());
3297                targets.push(target);
3298                stack.extend(active_bindings.iter().filter_map(|candidate| {
3299                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3300                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3301                    .then(|| candidate.resolved_target_components.clone())
3302                    .flatten()
3303                }));
3304            }
3305        }
3306    }
3307    targets
3308        .into_iter()
3309        .flat_map(|target| {
3310            let qualified_name = target.join("::");
3311            visibility
3312                .visible_identifier_candidates(file, name)
3313                .filter(move |candidate| {
3314                    candidate.is_function()
3315                        && type_owner_of(analyzer, candidate).is_none()
3316                        && cpp_name_for(candidate) == qualified_name
3317                        && visibility.declaration_visible_at(
3318                            analyzer,
3319                            file,
3320                            candidate,
3321                            reference_byte,
3322                        )
3323                })
3324                .cloned()
3325        })
3326        .collect()
3327}
3328
3329/// Collapse the candidates unqualified lookup found to one entry per logical
3330/// callable, keeping the first spelling of each.
3331///
3332/// A header declaration and its out-of-line body spell one parameter type
3333/// differently often enough that the persisted signature strings disagree, so
3334/// the string triple alone reports one C++ declaration as an overload set and
3335/// an unproven argument count then turns it into ambiguity.
3336/// `same_logical_callable` answers the string question first and resolves the
3337/// written parameter names only when the strings differ (#2010).
3338fn dedupe_callable_candidates(
3339    candidates: &mut Vec<CodeUnit>,
3340    analyzer: &CppGraphSource<'_>,
3341    visibility: &VisibilityIndex<'_>,
3342) {
3343    let mut deduped = Vec::with_capacity(candidates.len());
3344    for candidate in candidates.drain(..) {
3345        if !deduped
3346            .iter()
3347            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3348        {
3349            deduped.push(candidate);
3350        }
3351    }
3352    *candidates = deduped;
3353}
3354
3355fn resolve_callable_candidates(
3356    candidates: Vec<CodeUnit>,
3357    call_arity: Option<usize>,
3358    reference_byte: usize,
3359    analyzer: &CppGraphSource<'_>,
3360    visibility: &VisibilityIndex<'_>,
3361    file: &ProjectFile,
3362) -> BareCallTargetResolution {
3363    let mut candidates = candidates;
3364    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3365    if candidates.is_empty() {
3366        return BareCallTargetResolution::Missing;
3367    }
3368    let Some(call_arity) = call_arity else {
3369        // An unproven argument count cannot create ambiguity where lookup found
3370        // exactly one name binding: there is nothing to be ambiguous between.
3371        // C has no overloading at all, and a lone C++ candidate is the only
3372        // declaration unqualified lookup reached, so arity cannot pick another
3373        // one (#1811). Keeping it unproven discarded the proven candidate and
3374        // answered `ambiguous` with an empty definition list.
3375        if candidates.len() == 1 {
3376            return BareCallTargetResolution::FreeFunctions(candidates);
3377        }
3378        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3379    };
3380    let applicable = candidates
3381        .into_iter()
3382        .filter(|candidate| {
3383            visibility
3384                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3385                .is_some_and(|arity| arity.accepts(call_arity))
3386        })
3387        .collect::<Vec<_>>();
3388    if applicable.is_empty() {
3389        BareCallTargetResolution::CallableShadow
3390    } else {
3391        BareCallTargetResolution::FreeFunctions(applicable)
3392    }
3393}
3394
3395fn resolve_direct_type_candidates(
3396    candidates: Vec<(CodeUnit, Vec<String>)>,
3397    analyzer: &CppGraphSource<'_>,
3398    visibility: &VisibilityIndex<'_>,
3399    file: &ProjectFile,
3400) -> BareCallTargetResolution {
3401    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3402    for candidate in candidates {
3403        if !logical
3404            .iter()
3405            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3406        {
3407            logical.push(candidate);
3408        }
3409    }
3410    let [(target, components)] = logical.as_slice() else {
3411        return if logical.is_empty() {
3412            BareCallTargetResolution::Missing
3413        } else {
3414            BareCallTargetResolution::Ambiguous
3415        };
3416    };
3417    match visibility
3418        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3419    {
3420        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3421        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3422        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3423    }
3424}
3425
3426/// Resolve a direct using-declaration in the nearest concrete block before
3427/// class-member lookup. A block declaration such as `using std::swap;` adds
3428/// that name to the block scope and hides a same-named member. If the imported
3429/// target is not indexed, retain its structured path as boundary evidence.
3430#[allow(clippy::too_many_arguments)]
3431pub fn resolve_block_using_call_target(
3432    call: Node<'_>,
3433    function: Node<'_>,
3434    analyzer: &CppGraphSource<'_>,
3435    visibility: &VisibilityIndex<'_>,
3436    ordinary_type_imports: &OrdinaryTypeImportCell,
3437    file: &ProjectFile,
3438    source: &str,
3439) -> Option<BlockUsingCallTargetResolution> {
3440    if !matches!(function.kind(), "identifier" | "template_function") {
3441        return None;
3442    }
3443    let name = node_text(function_terminal_node(function), source);
3444    if name.is_empty() {
3445        return None;
3446    }
3447    let bindings = effective_using_bindings_for_name(
3448        visibility,
3449        ordinary_type_imports,
3450        file,
3451        function,
3452        source,
3453        name,
3454    );
3455    let block_bindings = bindings
3456        .iter()
3457        .filter(|binding| {
3458            binding.namespace_scope.is_none()
3459                && binding.block_scope
3460                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3461        })
3462        .collect::<Vec<_>>();
3463    if block_bindings.is_empty() {
3464        return None;
3465    }
3466    let lexical_scope =
3467        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3468            LexicalScopeResolution::Resolved(scope) => scope,
3469            LexicalScopeResolution::Ambiguous => {
3470                return Some(BlockUsingCallTargetResolution::Ambiguous);
3471            }
3472            LexicalScopeResolution::Missing => return None,
3473        };
3474    let reference_guards = preprocessor_guard_environment(function, source);
3475    let active = block_bindings
3476        .into_iter()
3477        .filter(|binding| {
3478            effective_using_binding_active(
3479                binding,
3480                function,
3481                &lexical_scope,
3482                reference_guards.as_ref(),
3483                visibility,
3484                file,
3485            )
3486        })
3487        .collect::<Vec<_>>();
3488    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3489    let at_tier = active
3490        .into_iter()
3491        .filter(|binding| binding.scope_depth == depth)
3492        .collect::<Vec<_>>();
3493    let callable_candidates = at_tier
3494        .iter()
3495        .flat_map(|binding| {
3496            binding_free_function_candidates(
3497                binding,
3498                &[],
3499                analyzer,
3500                visibility,
3501                file,
3502                name,
3503                call.start_byte(),
3504            )
3505        })
3506        .collect::<Vec<_>>();
3507    if !callable_candidates.is_empty() {
3508        return Some(BlockUsingCallTargetResolution::Target(
3509            resolve_callable_candidates(
3510                callable_candidates,
3511                visibility.call_arity_evidence(file, call, source).exact(),
3512                call.start_byte(),
3513                analyzer,
3514                visibility,
3515                file,
3516            ),
3517        ));
3518    }
3519    let type_candidates = at_tier
3520        .iter()
3521        .flat_map(|binding| {
3522            binding_type_candidates(
3523                binding,
3524                &[],
3525                analyzer,
3526                visibility,
3527                file,
3528                name,
3529                None,
3530                call.start_byte(),
3531            )
3532        })
3533        .collect::<Vec<_>>();
3534    if !type_candidates.is_empty() {
3535        return Some(BlockUsingCallTargetResolution::Target(
3536            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3537        ));
3538    }
3539
3540    let mut unindexed = Vec::new();
3541    for binding in at_tier {
3542        let Some(components) = binding.resolved_target_components.as_ref() else {
3543            continue;
3544        };
3545        if !unindexed.contains(components) {
3546            unindexed.push(components.clone());
3547        }
3548    }
3549    match unindexed.as_slice() {
3550        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3551        [] => None,
3552        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3553    }
3554}
3555
3556#[allow(clippy::too_many_arguments)]
3557pub fn resolve_bare_call_target(
3558    call: Node<'_>,
3559    function: Node<'_>,
3560    analyzer: &CppGraphSource<'_>,
3561    visibility: &VisibilityIndex<'_>,
3562    ordinary_type_imports: &OrdinaryTypeImportCell,
3563    file: &ProjectFile,
3564    source: &str,
3565) -> BareCallTargetResolution {
3566    if !matches!(function.kind(), "identifier" | "template_function") {
3567        return BareCallTargetResolution::Missing;
3568    }
3569    let terminal = function_terminal_node(function);
3570    let name = node_text(terminal, source);
3571    if name.is_empty() {
3572        return BareCallTargetResolution::Missing;
3573    }
3574    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3575    let lexical_scope =
3576        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3577            LexicalScopeResolution::Resolved(scope) => scope,
3578            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3579            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3580        };
3581    let type_resolution = resolve_type_node_lexically(
3582        function,
3583        analyzer,
3584        visibility,
3585        ordinary_type_imports,
3586        file,
3587        source,
3588    );
3589    let type_components = match &type_resolution {
3590        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3591        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3592    };
3593    let direct_type_resolution = visibility.resolve_type_components_lexically(
3594        analyzer,
3595        file,
3596        &[name.to_string()],
3597        false,
3598        &lexical_scope,
3599    );
3600    let direct_type_components = match &direct_type_resolution {
3601        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3602        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3603    };
3604    let has_explicit_template_arguments =
3605        cpp_template_reference_arguments(function, source).is_some();
3606    let bindings = effective_using_bindings_for_name(
3607        visibility,
3608        ordinary_type_imports,
3609        file,
3610        function,
3611        source,
3612        name,
3613    );
3614    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3615    // step re-descends from the root (#1927): with no bindings both filters
3616    // below select nothing, so the environment is never consulted.
3617    let function_guards = if bindings.is_empty() {
3618        None
3619    } else {
3620        preprocessor_guard_environment(function, source)
3621    };
3622    let active_bindings = bindings
3623        .iter()
3624        .filter(|binding| {
3625            effective_using_binding_active(
3626                binding,
3627                function,
3628                &lexical_scope,
3629                function_guards.as_ref(),
3630                visibility,
3631                file,
3632            )
3633        })
3634        .collect::<Vec<_>>();
3635    let transitive_bindings = bindings
3636        .iter()
3637        .filter(|binding| {
3638            effective_using_binding_guards_active(
3639                binding,
3640                function.start_byte(),
3641                function_guards.as_ref(),
3642                visibility,
3643                file,
3644            ) && (binding.namespace_scope.is_some()
3645                || (binding.scope_start <= function.start_byte()
3646                    && function.end_byte() <= binding.scope_end))
3647        })
3648        .collect::<Vec<_>>();
3649    let mut concrete_depths = active_bindings
3650        .iter()
3651        .filter(|binding| binding.namespace_scope.is_none())
3652        .map(|binding| binding.scope_depth)
3653        .collect::<Vec<_>>();
3654    concrete_depths.sort_unstable();
3655    concrete_depths.dedup();
3656    for depth in concrete_depths.into_iter().rev() {
3657        let at_tier = active_bindings
3658            .iter()
3659            .copied()
3660            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3661        let direct = at_tier
3662            .clone()
3663            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3664            .flat_map(|binding| {
3665                binding_free_function_candidates(
3666                    binding,
3667                    &transitive_bindings,
3668                    analyzer,
3669                    visibility,
3670                    file,
3671                    name,
3672                    call.start_byte(),
3673                )
3674            })
3675            .collect::<Vec<_>>();
3676        if !direct.is_empty() {
3677            return resolve_callable_candidates(
3678                direct,
3679                call_arity,
3680                call.start_byte(),
3681                analyzer,
3682                visibility,
3683                file,
3684            );
3685        }
3686        let direct_types = at_tier
3687            .clone()
3688            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3689            .flat_map(|binding| {
3690                binding_type_candidates(
3691                    binding,
3692                    &transitive_bindings,
3693                    analyzer,
3694                    visibility,
3695                    file,
3696                    name,
3697                    None,
3698                    call.start_byte(),
3699                )
3700            })
3701            .collect::<Vec<_>>();
3702        if !direct_types.is_empty() {
3703            // `resolve_direct_type_candidates` never consults the argument
3704            // count: it answers the one type the name binds to, or reports the
3705            // competing types. An unknown count therefore cannot make this
3706            // ambiguous (#1812).
3707            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3708        }
3709        let directives = at_tier
3710            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3711            .flat_map(|binding| {
3712                binding_free_function_candidates(
3713                    binding,
3714                    &transitive_bindings,
3715                    analyzer,
3716                    visibility,
3717                    file,
3718                    name,
3719                    call.start_byte(),
3720                )
3721            })
3722            .collect::<Vec<_>>();
3723        if !directives.is_empty() {
3724            return resolve_callable_candidates(
3725                directives,
3726                call_arity,
3727                call.start_byte(),
3728                analyzer,
3729                visibility,
3730                file,
3731            );
3732        }
3733    }
3734    for prefix_len in (0..=lexical_scope.len()).rev() {
3735        let mut qualified = lexical_scope[..prefix_len].to_vec();
3736        qualified.push(name.to_string());
3737        let same_name_resolves_to_type = direct_type_components
3738            .is_some_and(|components| components == qualified.as_slice())
3739            || type_components.is_some_and(|components| components == qualified.as_slice());
3740        let mut direct = visibility
3741            .visible_identifier_candidates(file, name)
3742            .filter(|candidate| {
3743                candidate.is_function()
3744                    && type_owner_of(analyzer, candidate).is_none()
3745                    && !(same_name_resolves_to_type
3746                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3747                            || has_explicit_template_arguments
3748                                && visibility
3749                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3750                    && cpp_name_for(candidate) == qualified.join("::")
3751                    && if analyzer.reference_uses_c_semantics(file) {
3752                        visibility.declaration_visible_for_c_forward_call(
3753                            analyzer,
3754                            file,
3755                            candidate,
3756                            call.start_byte(),
3757                        )
3758                    } else {
3759                        visibility.declaration_visible_at(
3760                            analyzer,
3761                            file,
3762                            candidate,
3763                            call.start_byte(),
3764                        )
3765                    }
3766            })
3767            .cloned()
3768            .collect::<Vec<_>>();
3769        let at_tier = active_bindings.iter().copied().filter(|binding| {
3770            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3771        });
3772        direct.extend(
3773            at_tier
3774                .clone()
3775                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3776                .flat_map(|binding| {
3777                    binding_free_function_candidates(
3778                        binding,
3779                        &transitive_bindings,
3780                        analyzer,
3781                        visibility,
3782                        file,
3783                        name,
3784                        call.start_byte(),
3785                    )
3786                }),
3787        );
3788        if !direct.is_empty() {
3789            return resolve_callable_candidates(
3790                direct,
3791                call_arity,
3792                call.start_byte(),
3793                analyzer,
3794                visibility,
3795                file,
3796            );
3797        }
3798        let mut direct_types = at_tier
3799            .clone()
3800            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3801            .flat_map(|binding| {
3802                binding_type_candidates(
3803                    binding,
3804                    &transitive_bindings,
3805                    analyzer,
3806                    visibility,
3807                    file,
3808                    name,
3809                    None,
3810                    call.start_byte(),
3811                )
3812            })
3813            .collect::<Vec<_>>();
3814        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3815            && let LexicalTypeResolution::Resolved {
3816                unit, components, ..
3817            } = &direct_type_resolution
3818        {
3819            direct_types.push((unit.clone(), components.clone()));
3820        }
3821        if !direct_types.is_empty() {
3822            // `resolve_direct_type_candidates` never consults the argument
3823            // count: it answers the one type the name binds to, or reports the
3824            // competing types. An unknown count therefore cannot make this
3825            // ambiguous (#1812).
3826            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3827        }
3828        let directives = at_tier
3829            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3830            .flat_map(|binding| {
3831                binding_free_function_candidates(
3832                    binding,
3833                    &transitive_bindings,
3834                    analyzer,
3835                    visibility,
3836                    file,
3837                    name,
3838                    call.start_byte(),
3839                )
3840            })
3841            .collect::<Vec<_>>();
3842        if !directives.is_empty() {
3843            return resolve_callable_candidates(
3844                directives,
3845                call_arity,
3846                call.start_byte(),
3847                analyzer,
3848                visibility,
3849                file,
3850            );
3851        }
3852        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3853            // The lexical type resolution below already answers with the single
3854            // type, or with its own ambiguity verdict; the argument count adds
3855            // nothing to that decision (#1812).
3856            return match type_resolution {
3857                LexicalTypeResolution::Resolved { unit, .. } => {
3858                    BareCallTargetResolution::Type(unit)
3859                }
3860                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3861                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3862            };
3863        }
3864    }
3865    // Every lookup tier is exhausted: no callable and no type candidate was
3866    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
3867    // at all, and its early return in get_definition preempted the same-file
3868    // macro fallback - so a call to a macro defined in the referencing file
3869    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
3870    // resolve once an unresolvable include made the argument count unknown.
3871    // A no-candidate outcome is Missing, which is what makes the fallback
3872    // reachable (#1812).
3873    match type_resolution {
3874        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3875        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3876        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3877    }
3878}
3879
3880fn static_qualifier_type_scopes<'tree>(
3881    node: Node<'tree>,
3882    ctx: &ScanCtx<'_>,
3883) -> Option<Vec<Node<'tree>>> {
3884    if !matches!(
3885        node.kind(),
3886        "qualified_identifier" | "scoped_type_identifier"
3887    ) {
3888        return None;
3889    }
3890    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
3891    // this root contains every structured component needed for prefix lookup.
3892    debug_assert!(!is_nested_type_node(node));
3893    let qualified = qualified_owner_components(node, ctx.source)?;
3894    static_qualifier_type_scopes_for_components(node, qualified, ctx)
3895}
3896
3897fn static_qualifier_type_scopes_for_components<'tree>(
3898    node: Node<'tree>,
3899    qualified: QualifiedOwnerComponents<'tree>,
3900    ctx: &ScanCtx<'_>,
3901) -> Option<Vec<Node<'tree>>> {
3902    if !qualified.global
3903        && qualified.names.first().is_some_and(|name| {
3904            name == ctx.spec.target.identifier()
3905                && qualified
3906                    .nodes
3907                    .first()
3908                    .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
3909        })
3910    {
3911        return None;
3912    }
3913    let mut matches = Vec::new();
3914    let mut inherited_injected_name_is_shadowed = false;
3915    for component_count in 1..=qualified.names.len() {
3916        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3917            node,
3918            &qualified.names[..component_count],
3919            qualified.global,
3920            &ctx.analyzer,
3921            ctx.visibility,
3922            &ctx.ordinary_type_imports,
3923            ctx.file,
3924            ctx.source,
3925            &ctx.spec.target,
3926            false,
3927            Some(&ctx.lexical_scope_cache),
3928        );
3929        match resolution {
3930            LexicalTypeResolution::Resolved {
3931                unit, candidates, ..
3932            } if (!ctx
3933                .analyzer
3934                .type_alias_provider()
3935                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3936                || ctx.visibility.external_type_candidate_visible_in_context(
3937                    &ctx.analyzer,
3938                    ctx.file,
3939                    &ctx.spec.target,
3940                    node,
3941                ))
3942                && (same_visible_symbol(&unit, &ctx.spec.target)
3943                    || candidates
3944                        .iter()
3945                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3946                && target_alias_candidates_visible(&candidates, node, ctx) =>
3947            {
3948                let matched =
3949                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3950                if !template_type_component_preserves_target(matched, &candidates, ctx) {
3951                    continue;
3952                }
3953                if !matches.iter().any(|existing: &Node<'_>| {
3954                    existing.start_byte() == matched.start_byte()
3955                        && existing.end_byte() == matched.end_byte()
3956                }) {
3957                    matches.push(matched);
3958                }
3959            }
3960            // The ordinary lexical resolver can remain ambiguous when the
3961            // qualified terminal is an alias whose canonical target is not
3962            // indexed (for example, `Hash::Digest` aliases an external
3963            // `std::array`). The target-guided path below still requires one
3964            // physically visible logical class for every emitted prefix.
3965            LexicalTypeResolution::Ambiguous => {
3966                return (!inherited_injected_name_is_shadowed)
3967                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3968                    .flatten()
3969                    .map(|scope| vec![scope])
3970                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3971            }
3972            LexicalTypeResolution::Resolved { .. } => {
3973                if let Some(matched) =
3974                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3975                {
3976                    matches.push(matched);
3977                }
3978                inherited_injected_name_is_shadowed |= component_count == 1;
3979            }
3980            LexicalTypeResolution::Missing => {
3981                if let Some(matched) =
3982                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3983                {
3984                    matches.push(matched);
3985                }
3986            }
3987        }
3988    }
3989    if matches.is_empty() {
3990        (!inherited_injected_name_is_shadowed)
3991            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3992            .flatten()
3993            .map(|scope| vec![scope])
3994            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3995    } else {
3996        Some(matches)
3997    }
3998}
3999
4000/// Recover a class owner in a qualified expression when guard-aware lookup
4001/// cannot prove the owner. Keep the hit on the owner component, not the member.
4002fn target_guided_unproven_qualified_value_owner_scope<'tree>(
4003    node: Node<'tree>,
4004    ctx: &ScanCtx<'_>,
4005) -> Option<Node<'tree>> {
4006    let target = physically_visible_type_target(ctx)?;
4007    if !target.is_class() {
4008        return None;
4009    }
4010    let qualified = qualified_owner_components(node, ctx.source)?;
4011    let lexical_scope = match enclosing_lexical_scope_components(
4012        node,
4013        &ctx.analyzer,
4014        ctx.visibility,
4015        ctx.file,
4016        ctx.source,
4017    ) {
4018        LexicalScopeResolution::Resolved(scope) => scope,
4019        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4020            enclosing_namespace_components(node, ctx.source)
4021        }
4022    };
4023    let LexicalTypeResolution::Resolved {
4024        unit, candidates, ..
4025    } = ctx.visibility.resolve_type_components_lexically_for_target(
4026        &ctx.analyzer,
4027        ctx.file,
4028        &qualified.names,
4029        qualified.global,
4030        &lexical_scope,
4031        target,
4032    )
4033    else {
4034        return None;
4035    };
4036    (same_visible_symbol(&unit, target)
4037        || candidates
4038            .iter()
4039            .any(|candidate| same_visible_symbol(candidate, target)))
4040    .then(|| qualified.nodes.last().copied())
4041    .flatten()
4042}
4043
4044/// Resolve a nested class-owned alias when the indexed alias path is not a
4045/// standalone type candidate. The C++ index stores `basic_json::type_error`
4046/// as a synthetic child of `basic_json`, while source can qualify it through
4047/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4048/// then canonicalize the structured member alias against the requested type.
4049fn target_guided_nested_alias_type_scope<'tree>(
4050    node: Node<'tree>,
4051    qualified: &QualifiedOwnerComponents<'tree>,
4052    component_count: usize,
4053    ctx: &ScanCtx<'_>,
4054) -> Option<Node<'tree>> {
4055    if component_count < 2 {
4056        return None;
4057    }
4058    let (owner_components, member_name) =
4059        qualified.names[..component_count].split_at(component_count - 1);
4060    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4061        node,
4062        owner_components,
4063        qualified.global,
4064        &ctx.analyzer,
4065        ctx.visibility,
4066        &ctx.ordinary_type_imports,
4067        ctx.file,
4068        ctx.source,
4069    ) else {
4070        return None;
4071    };
4072    let member_name = member_name.first()?;
4073    let alias_provider = ctx.analyzer.type_alias_provider()?;
4074    ctx.visibility
4075        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4076        .into_iter()
4077        .filter(|member| alias_provider.is_type_alias(member))
4078        .find(|member| {
4079            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4080                &ctx.analyzer,
4081                ctx.file,
4082                member,
4083                node,
4084            ) || ctx
4085                .visibility
4086                .external_type_candidate_guard_compatible_in_context(
4087                    &ctx.analyzer,
4088                    ctx.file,
4089                    member,
4090                    node,
4091                );
4092            if !member_visible {
4093                return false;
4094            }
4095            same_visible_symbol(member, &ctx.spec.target)
4096                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4097        })
4098        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4099}
4100
4101fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4102    if ctx.visibility.structured_class_alias_resolves_to_target(
4103        &ctx.analyzer,
4104        ctx.file,
4105        candidate,
4106        &ctx.spec.target,
4107    ) {
4108        return ctx.spec.target.clone();
4109    }
4110    let structured = ctx
4111        .visibility
4112        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4113    if let Some(canonical) = structured
4114        .as_ref()
4115        .filter(|canonical| !same_visible_symbol(canonical, candidate))
4116    {
4117        return canonical.clone();
4118    }
4119    structured.unwrap_or_else(|| candidate.clone())
4120}
4121
4122/// Preserve the alias component of a qualified reference when the alias target
4123/// is a dependent nested type and forward lookup retains its primary template
4124/// as the bounded identity.
4125fn target_guided_dependent_alias_qualifier_scope<'tree>(
4126    node: Node<'tree>,
4127    ctx: &ScanCtx<'_>,
4128) -> Option<Node<'tree>> {
4129    if !matches!(
4130        node.kind(),
4131        "qualified_identifier" | "scoped_type_identifier"
4132    ) {
4133        return None;
4134    }
4135    let target = physically_visible_type_target(ctx)?;
4136    let alias_provider = ctx.analyzer.type_alias_provider()?;
4137    if !target.is_class() || alias_provider.is_type_alias(target) {
4138        return None;
4139    }
4140    let nodes = cpp_name_component_nodes(node)?;
4141    let names = nodes
4142        .iter()
4143        .map(|component| node_text(*component, ctx.source).to_string())
4144        .collect::<Vec<_>>();
4145    let global = is_globally_qualified_cpp_name(node);
4146    let lexical_scope = match enclosing_lexical_scope_components(
4147        node,
4148        &ctx.analyzer,
4149        ctx.visibility,
4150        ctx.file,
4151        ctx.source,
4152    ) {
4153        LexicalScopeResolution::Resolved(scope) => scope,
4154        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4155            enclosing_namespace_components(node, ctx.source)
4156        }
4157    };
4158    for component_count in 1..=names.len() {
4159        let components = &names[..component_count];
4160        let name = components.last()?;
4161        let candidates = ctx
4162            .visibility
4163            .visible_identifier_candidates(ctx.file, name)
4164            .filter(|candidate| alias_provider.is_type_alias(candidate))
4165            .filter(|candidate| {
4166                ctx.visibility.is_physically_visible(ctx.file, candidate)
4167                    && ctx
4168                        .visibility
4169                        .external_type_candidate_guard_compatible_in_context(
4170                            &ctx.analyzer,
4171                            ctx.file,
4172                            candidate,
4173                            node,
4174                        )
4175            })
4176            .filter(|candidate| {
4177                let candidate_components = canonical_cpp_scope_components(candidate);
4178                lexical_component_tiers(components, global, &lexical_scope)
4179                    .any(|tier| tier == candidate_components)
4180                    || (component_count == 1
4181                        && member_alias_owner_matches_reference_for(
4182                            candidate,
4183                            nodes[component_count - 1],
4184                            ctx,
4185                        ))
4186            })
4187            .filter(|candidate| {
4188                ctx.visibility.structured_class_alias_path_preserves_target(
4189                    &ctx.analyzer,
4190                    ctx.file,
4191                    candidate,
4192                    target,
4193                )
4194            });
4195        let mut aliases: Vec<&CodeUnit> = Vec::new();
4196        for candidate in candidates {
4197            if !aliases
4198                .iter()
4199                .any(|existing| same_logical_symbol(existing, candidate))
4200            {
4201                aliases.push(candidate);
4202            }
4203        }
4204        if aliases.len() == 1 {
4205            return nodes.get(component_count - 1).copied();
4206        }
4207        if aliases.len() > 1 {
4208            return None;
4209        }
4210    }
4211    None
4212}
4213
4214/// Preserve an unqualified class-owned alias when its dependent target path
4215/// retains the requested primary template as the bounded forward identity.
4216fn target_guided_dependent_class_alias_leaf<'tree>(
4217    node: Node<'tree>,
4218    ctx: &ScanCtx<'_>,
4219) -> Option<Node<'tree>> {
4220    if node.kind() != "type_identifier"
4221        || is_declaration_name(node)
4222        || local_type_name_shadows(node, ctx)
4223    {
4224        return None;
4225    }
4226    let target = physically_visible_type_target(ctx)?;
4227    let alias_provider = ctx.analyzer.type_alias_provider()?;
4228    if !target.is_class() || alias_provider.is_type_alias(target) {
4229        return None;
4230    }
4231    let name = node_text(node, ctx.source);
4232    let aliases = ctx
4233        .visibility
4234        .visible_identifier_candidates(ctx.file, name)
4235        .filter(|candidate| alias_provider.is_type_alias(candidate))
4236        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4237        .filter(|candidate| {
4238            ctx.visibility.is_physically_visible(ctx.file, candidate)
4239                && ctx
4240                    .visibility
4241                    .external_type_candidate_guard_compatible_in_context(
4242                        &ctx.analyzer,
4243                        ctx.file,
4244                        candidate,
4245                        node,
4246                    )
4247        })
4248        .filter(|candidate| {
4249            ctx.visibility.structured_class_alias_path_preserves_target(
4250                &ctx.analyzer,
4251                ctx.file,
4252                candidate,
4253                target,
4254            )
4255        })
4256        .collect::<Vec<_>>();
4257    matches!(aliases.as_slice(), [_]).then_some(node)
4258}
4259
4260/// Recover a namespace alias whose guard state blocks ordinary visibility.
4261/// Require one visible canonical target and an exact structured alias path.
4262fn target_guided_unproven_alias_type_reference<'tree>(
4263    node: Node<'tree>,
4264    candidates: &[CodeUnit],
4265    ctx: &ScanCtx<'_>,
4266) -> Option<Node<'tree>> {
4267    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4268    let target = physically_visible_type_target(ctx)?;
4269    if !target.is_class() {
4270        return None;
4271    }
4272    let alias_provider = ctx.analyzer.type_alias_provider()?;
4273    let (components, _) = type_reference_components(node, ctx.source)?;
4274    let hit = template_arguments
4275        .as_ref()
4276        .and_then(|_| template_reference_name_node(node))
4277        .map(function_terminal_node)
4278        .unwrap_or_else(|| function_terminal_node(node));
4279    candidates
4280        .iter()
4281        .filter(|candidate| {
4282            alias_provider.is_type_alias(candidate)
4283                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4284                && canonical_cpp_scope_components(candidate) == components
4285        })
4286        .find(|candidate| {
4287            template_arguments.as_ref().map_or_else(
4288                || {
4289                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4290                        || ctx.visibility.structured_alias_primary_preserves_target(
4291                            &ctx.analyzer,
4292                            ctx.file,
4293                            candidate,
4294                            target,
4295                        )
4296                },
4297                |arguments| {
4298                    ctx.visibility.template_alias_arguments_preserve_target(
4299                        &ctx.analyzer,
4300                        ctx.file,
4301                        candidate,
4302                        arguments,
4303                        target,
4304                    )
4305                },
4306            )
4307        })
4308        .map(|_| hit)
4309}
4310
4311fn target_alias_candidates_visible(
4312    candidates: &[CodeUnit],
4313    reference: Node<'_>,
4314    ctx: &ScanCtx<'_>,
4315) -> bool {
4316    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4317        return true;
4318    };
4319    if candidates.iter().any(|candidate| {
4320        !alias_provider.is_type_alias(candidate)
4321            && ctx.visibility.same_template_member_identity(
4322                &ctx.analyzer,
4323                candidate,
4324                &ctx.spec.target,
4325            )
4326    }) {
4327        return true;
4328    }
4329    let target_aliases = candidates
4330        .iter()
4331        .filter(|candidate| {
4332            alias_provider.is_type_alias(candidate)
4333                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4334        })
4335        .collect::<Vec<_>>();
4336    target_aliases.is_empty()
4337        || target_aliases
4338            .iter()
4339            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4340}
4341
4342fn type_candidate_visible_at_reference(
4343    candidate: &CodeUnit,
4344    reference: Node<'_>,
4345    ctx: &ScanCtx<'_>,
4346) -> bool {
4347    let class_owned_alias = ctx
4348        .analyzer
4349        .type_alias_provider()
4350        .is_some_and(|provider| provider.is_type_alias(candidate))
4351        && ctx
4352            .analyzer
4353            .parent_of(candidate)
4354            .is_some_and(|owner| owner.is_class());
4355    if class_owned_alias {
4356        let conditional_family = ctx
4357            .visibility
4358            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4359        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4360            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4361            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4362        let guard_match = ctx
4363            .visibility
4364            .external_type_candidate_guard_compatible_in_context(
4365                &ctx.analyzer,
4366                ctx.file,
4367                candidate,
4368                reference,
4369            );
4370        let general_match = conditional_family
4371            && ctx.visibility.external_type_candidate_visible_in_context(
4372                &ctx.analyzer,
4373                ctx.file,
4374                candidate,
4375                reference,
4376            );
4377        return owner_match && (guard_match || general_match);
4378    }
4379    ctx.visibility.external_type_candidate_visible_in_context(
4380        &ctx.analyzer,
4381        ctx.file,
4382        candidate,
4383        reference,
4384    )
4385}
4386
4387fn unqualified_reference_selects_inherited_alias(
4388    candidate: &CodeUnit,
4389    reference: Node<'_>,
4390    ctx: &ScanCtx<'_>,
4391) -> bool {
4392    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4393        return false;
4394    };
4395    if global || components.len() != 1 {
4396        return false;
4397    }
4398    matches!(
4399        resolve_type_node_lexically_for_target(
4400            reference,
4401            &ctx.analyzer,
4402            ctx.visibility,
4403            &ctx.ordinary_type_imports,
4404            ctx.file,
4405            ctx.source,
4406            candidate,
4407            Some(&ctx.lexical_scope_cache),
4408            ctx.recovered_sentinel_scope(reference).as_deref(),
4409        ),
4410        LexicalTypeResolution::Resolved {
4411            ref unit,
4412            ref candidates,
4413            ..
4414        } if ctx
4415            .visibility
4416            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4417            || candidates.iter().any(|resolved| {
4418                ctx.visibility.same_template_member_identity(
4419                    &ctx.analyzer,
4420                    resolved,
4421                    candidate,
4422                )
4423            })
4424    )
4425}
4426
4427fn qualified_reference_selects_type_candidate(
4428    candidate: &CodeUnit,
4429    reference: Node<'_>,
4430    ctx: &ScanCtx<'_>,
4431) -> bool {
4432    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4433        return false;
4434    };
4435    if components.len() < 2 {
4436        return false;
4437    }
4438    let candidate_components = canonical_cpp_scope_components(candidate);
4439    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4440        match enclosing_lexical_scope_components(
4441            reference,
4442            &ctx.analyzer,
4443            ctx.visibility,
4444            ctx.file,
4445            ctx.source,
4446        ) {
4447            LexicalScopeResolution::Resolved(scope) => scope,
4448            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4449                enclosing_namespace_components(reference, ctx.source)
4450            }
4451        }
4452    });
4453    lexical_component_tiers(&components, global, &lexical_scope)
4454        .any(|qualified| qualified == candidate_components)
4455}
4456
4457fn qualified_type_component_hit_node<'tree>(
4458    component: Node<'tree>,
4459    qualified: Node<'tree>,
4460) -> Node<'tree> {
4461    let mut current = component;
4462    while let Some(parent) = current.parent() {
4463        let is_type_name = matches!(
4464            parent.kind(),
4465            "template_type"
4466                | "qualified_identifier"
4467                | "scoped_identifier"
4468                | "scoped_type_identifier"
4469        ) && parent
4470            .child_by_field_name("name")
4471            .is_some_and(|name| same_node(name, current));
4472        if !is_type_name {
4473            break;
4474        }
4475        current = parent;
4476        if same_node(parent, qualified) {
4477            break;
4478        }
4479    }
4480    current
4481}
4482
4483fn template_type_component_preserves_target(
4484    node: Node<'_>,
4485    candidates: &[CodeUnit],
4486    ctx: &ScanCtx<'_>,
4487) -> bool {
4488    template_reference_candidates_select_target(
4489        node,
4490        candidates,
4491        &ctx.analyzer,
4492        ctx.visibility,
4493        ctx.file,
4494        ctx.source,
4495        &ctx.spec.target,
4496    )
4497}
4498
4499fn template_reference_candidates_select_target(
4500    node: Node<'_>,
4501    candidates: &[CodeUnit],
4502    analyzer: &CppGraphSource<'_>,
4503    visibility: &VisibilityIndex<'_>,
4504    file: &ProjectFile,
4505    source: &str,
4506    target: &CodeUnit,
4507) -> bool {
4508    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4509        return !visibility.is_template_specialization(target);
4510    };
4511    let direct_template_name =
4512        template_reference_name_node(node).map(|name| node_text(name, source));
4513    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4514        analyzer.type_alias_provider().is_some_and(|provider| {
4515            visibility
4516                .visible_identifier_candidates(file, name)
4517                .filter(|candidate| provider.is_type_alias(candidate))
4518                .any(|candidate| {
4519                    visibility.template_alias_arguments_preserve_target(
4520                        analyzer, file, candidate, &arguments, target,
4521                    )
4522                })
4523        })
4524    });
4525    named_alias_selects_target
4526        || candidates.iter().any(|candidate| {
4527            (same_visible_symbol(candidate, target)
4528                && visibility.is_primary_template(target)
4529                && direct_template_name == Some(candidate.identifier()))
4530                || visibility.template_alias_arguments_preserve_target(
4531                    analyzer, file, candidate, &arguments, target,
4532                )
4533                || visibility
4534                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4535                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4536        })
4537}
4538
4539fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4540    let template = if node.kind() == "template_type" {
4541        node
4542    } else {
4543        node.child_by_field_name("name")
4544            .filter(|name| name.kind() == "template_type")?
4545    };
4546    template.child_by_field_name("name")
4547}
4548
4549fn type_resolution_matches_target(
4550    node: Node<'_>,
4551    unit: &CodeUnit,
4552    candidates: &[CodeUnit],
4553    ctx: &ScanCtx<'_>,
4554) -> bool {
4555    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4556}
4557
4558fn type_resolution_matches_unit_target(
4559    node: Node<'_>,
4560    unit: &CodeUnit,
4561    candidates: &[CodeUnit],
4562    target: &CodeUnit,
4563    ctx: &ScanCtx<'_>,
4564) -> bool {
4565    target_alias_candidates_visible(candidates, node, ctx)
4566        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4567}
4568
4569/// The identity half of the type-resolution match, without the alias
4570/// visibility gate.
4571///
4572/// Use it only on the without-visibility fallback path, which reports an
4573/// unproven hit. An alias spelling does not contain the target identifier, so
4574/// the name-mention fallback can never recover a rejected alias reference: the
4575/// site would disappear instead of degrading to a reviewable hit.
4576fn type_resolution_identifies_unit_target(
4577    node: Node<'_>,
4578    unit: &CodeUnit,
4579    candidates: &[CodeUnit],
4580    target: &CodeUnit,
4581    ctx: &ScanCtx<'_>,
4582) -> bool {
4583    if !template_alias_owner_matches_reference(node, target, ctx) {
4584        return false;
4585    }
4586    if ctx.visibility.is_template_specialization(target)
4587        && cpp_template_reference_arguments(node, ctx.source).is_some()
4588    {
4589        let selected_unit =
4590            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4591                ctx.visibility
4592                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4593                    .ok()
4594            });
4595        return selected_unit
4596            .as_ref()
4597            .is_some_and(|selected| same_visible_symbol(selected, target))
4598            || template_reference_candidates_select_target(
4599                node,
4600                candidates,
4601                &ctx.analyzer,
4602                ctx.visibility,
4603                ctx.file,
4604                ctx.source,
4605                target,
4606            );
4607    }
4608    unit == target
4609        || ctx
4610            .visibility
4611            .same_template_member_identity(&ctx.analyzer, unit, target)
4612        || ctx.visibility.c_tag_declaration_family_matches_target(
4613            &ctx.analyzer,
4614            ctx.file,
4615            &candidates.iter().collect::<Vec<_>>(),
4616            target,
4617        )
4618        || ctx.visibility.structured_class_alias_resolves_to_target(
4619            &ctx.analyzer,
4620            ctx.file,
4621            unit,
4622            target,
4623        )
4624        || candidates.iter().any(|candidate| {
4625            ctx.visibility
4626                .same_template_member_identity(&ctx.analyzer, candidate, target)
4627                || ctx.visibility.structured_class_alias_resolves_to_target(
4628                    &ctx.analyzer,
4629                    ctx.file,
4630                    candidate,
4631                    target,
4632                )
4633        })
4634}
4635
4636/// Keep a member alias attached to the class specialization that declares it.
4637/// A target-guided lexical lookup can otherwise retain the primary alias when
4638/// the source reference is inside a partial specialization with the same
4639/// unqualified alias name. Compare the indexed template identities instead of
4640/// rendered text or suffixes.
4641fn template_alias_owner_matches_reference(
4642    node: Node<'_>,
4643    target: &CodeUnit,
4644    ctx: &ScanCtx<'_>,
4645) -> bool {
4646    if !ctx
4647        .analyzer
4648        .type_alias_provider()
4649        .is_some_and(|provider| provider.is_type_alias(target))
4650    {
4651        return true;
4652    }
4653    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4654        return true;
4655    };
4656    if !target_owner.is_class() {
4657        return true;
4658    }
4659    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4660        return true;
4661    };
4662    if !ctx.visibility.is_template_specialization(&target_owner)
4663        && !ctx.visibility.is_template_specialization(&reference_owner)
4664    {
4665        return true;
4666    }
4667    same_visible_symbol(&target_owner, &reference_owner)
4668}
4669
4670fn inherited_injected_class_qualifier_scope<'tree>(
4671    node: Node<'tree>,
4672    ctx: &ScanCtx<'_>,
4673) -> Option<Node<'tree>> {
4674    let qualified = qualified_owner_components(node, ctx.source)?;
4675    if qualified.global || qualified.names.is_empty() {
4676        return None;
4677    }
4678    let injected_name = &qualified.names[0];
4679    if !ctx.spec.target.is_class()
4680        || ctx.spec.target.identifier() != injected_name
4681        || physically_visible_type_target(ctx).is_none()
4682    {
4683        return None;
4684    }
4685    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4686    let owner = ctx.visibility.inherited_injected_class_owner(
4687        &ctx.analyzer,
4688        ctx.file,
4689        &enclosing_owner,
4690        injected_name,
4691    )?;
4692    same_visible_symbol(&owner, &ctx.spec.target)
4693        .then(|| qualified.nodes.first().copied())
4694        .flatten()
4695}
4696
4697/// Resolve each qualified type component against the inverse target while
4698/// preserving C++ lexical-tier precedence and structured alias identity.
4699fn target_guided_qualifier_type_scopes<'tree>(
4700    node: Node<'tree>,
4701    ctx: &ScanCtx<'_>,
4702) -> Option<Vec<Node<'tree>>> {
4703    if !matches!(
4704        node.kind(),
4705        "qualified_identifier" | "scoped_type_identifier"
4706    ) {
4707        return None;
4708    }
4709    let target = physically_visible_type_target(ctx)?;
4710    let qualified = qualified_owner_components(node, ctx.source)?;
4711    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4712    // scope before falling back to suffix recovery.  A short unqualified
4713    // owner can have a same-spelled class in a nested namespace (for example
4714    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4715    // both as possible owners and then fails closed, even though the
4716    // translation unit's lexical scope selects the global class.  Keep the
4717    // suffix path for malformed namespace sentinels, where the parser does
4718    // not expose every indexed scope component.
4719    let lexical_scope = match enclosing_lexical_scope_components(
4720        node,
4721        &ctx.analyzer,
4722        ctx.visibility,
4723        ctx.file,
4724        ctx.source,
4725    ) {
4726        LexicalScopeResolution::Resolved(scope) => scope,
4727        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4728            enclosing_namespace_components(node, ctx.source)
4729        }
4730    };
4731    let indexed_owner_scope =
4732        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4733    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4734    let mut matches = Vec::new();
4735    for component_count in 1..=qualified.names.len() {
4736        let components = &qualified.names[..component_count];
4737        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4738            .collect::<Vec<_>>();
4739        let name = components.last()?;
4740        let mut candidates = Vec::new();
4741        let mut exact_candidates = Vec::new();
4742        for candidate in ctx
4743            .visibility
4744            .visible_identifier_candidates(ctx.file, name)
4745            .filter(|candidate| candidate.is_class())
4746            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4747        {
4748            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4749                brokk_bifrost_core::analyzer::Language::Cpp,
4750                &cpp_name_for(candidate),
4751            );
4752            if !candidate_components.ends_with(components)
4753                || candidates
4754                    .iter()
4755                    .any(|existing| same_logical_symbol(existing, candidate))
4756            {
4757                continue;
4758            }
4759            let exact_lexical_scope = lexical_tiers
4760                .iter()
4761                .any(|expected| expected == &candidate_components);
4762            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4763            let structured_owner_match = indexed_owner_scope
4764                .as_ref()
4765                .is_some_and(|owner| owner.starts_with(candidate_owner))
4766                || recovered_owner_scope
4767                    .as_ref()
4768                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4769            let class_alias_owner_match = ctx
4770                .analyzer
4771                .type_alias_provider()
4772                .is_some_and(|provider| provider.is_type_alias(candidate))
4773                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4774            let macro_namespace_owner_match = is_declaration_name(node)
4775                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4776            if components.len() == 1
4777                && candidate_components != components
4778                && !exact_lexical_scope
4779                && !structured_owner_match
4780                && !class_alias_owner_match
4781                && !macro_namespace_owner_match
4782            {
4783                continue;
4784            }
4785            candidates.push(candidate.clone());
4786            if exact_lexical_scope {
4787                exact_candidates.push(candidate.clone());
4788            }
4789        }
4790        if !exact_candidates.is_empty() {
4791            candidates = exact_candidates;
4792        }
4793        // A typedef spelling can qualify nested C++ members while forward
4794        // lookup canonicalizes that spelling to its underlying class. Preserve
4795        // the exact alias prefix only when structured alias resolution proves
4796        // that it denotes this inverse target.
4797        let canonical_alias_target_matches = matches!(
4798            candidates.as_slice(),
4799            [candidate]
4800                if ctx
4801                    .analyzer
4802                    .type_alias_provider()
4803                    .is_some_and(|provider| provider.is_type_alias(candidate))
4804                    && type_candidate_visible_at_reference(candidate, node, ctx)
4805                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4806                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4807                        brokk_bifrost_core::analyzer::Language::Cpp,
4808                        &cpp_name_for(candidate),
4809                    ) == components
4810                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
4811        );
4812        let direct_alias_target = ctx
4813            .analyzer
4814            .type_alias_provider()
4815            .is_some_and(|provider| provider.is_type_alias(target))
4816            && candidates
4817                .iter()
4818                .any(|candidate| same_symbol(candidate, target));
4819        let unique_target = matches!(
4820            candidates.as_slice(),
4821            [candidate] if same_visible_symbol(candidate, target)
4822        );
4823        if direct_alias_target || unique_target || canonical_alias_target_matches {
4824            let matched = if ctx
4825                .analyzer
4826                .type_alias_provider()
4827                .is_some_and(|provider| provider.is_type_alias(target))
4828                && ctx
4829                    .analyzer
4830                    .parent_of(target)
4831                    .is_some_and(|owner| owner.is_class())
4832                && ctx
4833                    .visibility
4834                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4835                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4836            {
4837                // The alias declaration owns the terminal component. Keep
4838                // the inverse range narrow so `MathLib::bigint` records the
4839                // `bigint` token, not the complete qualified owner path.
4840                qualified.nodes[component_count - 1]
4841            } else {
4842                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4843            };
4844            if template_type_component_preserves_target(matched, &candidates, ctx) {
4845                matches.push(matched);
4846            }
4847        }
4848    }
4849    (!matches.is_empty()).then_some(matches)
4850}
4851
4852fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4853    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4854        return false;
4855    };
4856    ctx.visibility
4857        .visible_identifier_candidates(ctx.file, target.identifier())
4858        .any(|candidate| {
4859            alias_provider.is_type_alias(candidate)
4860                && candidate.identifier() == target.identifier()
4861                && !same_visible_symbol(candidate, target)
4862                && ctx
4863                    .analyzer
4864                    .parent_of(candidate)
4865                    .is_some_and(|owner| owner.is_class())
4866        })
4867}
4868
4869/// Recover an out-of-line owner when the owner declaration and the reference
4870/// use different unknown preprocessor guards. Keep this result unproven.
4871fn target_guided_unproven_out_of_line_owner<'tree>(
4872    node: Node<'tree>,
4873    ctx: &ScanCtx<'_>,
4874) -> Option<Node<'tree>> {
4875    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4876        || !is_declaration_name(node)
4877    {
4878        return None;
4879    }
4880    let target = physically_visible_type_target(ctx)?;
4881    if !target.is_class() {
4882        return None;
4883    }
4884    let qualified = qualified_owner_components(node, ctx.source)?;
4885    let target_components = canonical_cpp_scope_components(target);
4886    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4887    let parser_scope = enclosing_namespace_components(node, ctx.source);
4888    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4889        if !parser_scope.is_empty() || target_namespace.is_empty() {
4890            Some(parser_scope)
4891        } else {
4892            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4893        }
4894    })?;
4895    if has_malformed_wrapper_function_definition_ancestor(node)
4896        && target_namespace.starts_with(&scope)
4897        && target_namespace.len() > scope.len()
4898    {
4899        scope = target_namespace.to_vec();
4900    }
4901    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4902        .any(|components| components == target_components)
4903    {
4904        return None;
4905    }
4906    let owner_name = qualified.names.last()?;
4907    let candidates = ctx
4908        .visibility
4909        .visible_identifier_candidates(ctx.file, owner_name)
4910        .filter(|candidate| {
4911            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4912        })
4913        .collect::<Vec<_>>();
4914    if candidates.is_empty()
4915        || candidates
4916            .iter()
4917            .any(|candidate| !same_visible_symbol(candidate, target))
4918    {
4919        return None;
4920    }
4921    qualified.nodes.last().copied()
4922}
4923
4924fn macro_namespace_scope_matches(
4925    candidate_owner: &[String],
4926    node: Node<'_>,
4927    ctx: &ScanCtx<'_>,
4928) -> bool {
4929    let namespace = enclosing_namespace_components(node, ctx.source);
4930    if namespace.is_empty() || candidate_owner.is_empty() {
4931        return false;
4932    }
4933    let mut expanded_owner = Vec::new();
4934    for component in candidate_owner {
4935        if let Some(replacement) =
4936            ctx.visibility
4937                .object_macro_replacement_at(ctx.file, component, node.start_byte())
4938        {
4939            let replacement_components =
4940                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4941                    brokk_bifrost_core::analyzer::Language::Cpp,
4942                    &replacement,
4943                );
4944            if replacement_components.is_empty() {
4945                return false;
4946            }
4947            expanded_owner.extend(replacement_components);
4948        } else {
4949            expanded_owner.push(component.clone());
4950        }
4951    }
4952    expanded_owner == namespace
4953}
4954
4955fn target_guided_missing_type_leaf<'tree>(
4956    node: Node<'tree>,
4957    ctx: &ScanCtx<'_>,
4958) -> Option<Node<'tree>> {
4959    physically_visible_type_target(ctx)?;
4960    target_guided_missing_dependent_nested_type_leaf(node, ctx)
4961        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
4962        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
4963        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
4964        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
4965        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4966}
4967
4968/// Recover a bare class-owned alias whose structured canonical target is the
4969/// requested type. The class owner must enclose the reference, and every alias
4970/// with that spelling in the owner chain must preserve the same target.
4971fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4972    node: Node<'tree>,
4973    ctx: &ScanCtx<'_>,
4974) -> Option<Node<'tree>> {
4975    if node.kind() != "type_identifier"
4976        || is_declaration_name(node)
4977        || local_type_name_shadows(node, ctx)
4978    {
4979        return None;
4980    }
4981    let alias_provider = ctx.analyzer.type_alias_provider()?;
4982    let name = node_text(node, ctx.source);
4983    let aliases = ctx
4984        .visibility
4985        .visible_identifier_candidates(ctx.file, name)
4986        .filter(|candidate| alias_provider.is_type_alias(candidate))
4987        .filter(|candidate| {
4988            ctx.analyzer
4989                .parent_of(candidate)
4990                .is_some_and(|owner| owner.is_class())
4991        })
4992        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4993        .filter(|candidate| {
4994            ctx.visibility
4995                .external_type_candidate_guard_compatible_in_context(
4996                    &ctx.analyzer,
4997                    ctx.file,
4998                    candidate,
4999                    node,
5000                )
5001        })
5002        .collect::<Vec<_>>();
5003    (!aliases.is_empty()
5004        && aliases.iter().all(|candidate| {
5005            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5006        }))
5007    .then_some(node)
5008}
5009
5010/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5011/// type only when the indexed class owner proves the alias declaration. This
5012/// narrow path covers malformed class bodies without accepting unrelated aliases.
5013fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5014    node: Node<'tree>,
5015    ctx: &ScanCtx<'_>,
5016) -> Option<Node<'tree>> {
5017    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5018        matches!(
5019            declaration.kind(),
5020            "parameter_declaration" | "optional_parameter_declaration"
5021        )
5022    });
5023    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5024        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5025    });
5026    if !parameter && !placement_new_type {
5027        return None;
5028    }
5029    if !ctx
5030        .analyzer
5031        .type_alias_provider()
5032        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5033        || !type_alias_owner_matches_structured_reference(node, ctx)
5034    {
5035        return None;
5036    }
5037    target_guided_missing_declaration_type_leaf(node, ctx)
5038}
5039
5040/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5041/// sentinel prevents ordinary lexical resolution. The indexed target must have
5042/// an indexed class parent, the structured owner path must compose with one
5043/// proven lexical namespace source, and every visible candidate at that exact
5044/// owner path must be the indexed parent. This keeps the fallback owner-based;
5045/// a same-spelled nested type under another template remains unproven.
5046fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5047    node: Node<'tree>,
5048    ctx: &ScanCtx<'_>,
5049) -> Option<Node<'tree>> {
5050    if !matches!(
5051        node.kind(),
5052        "qualified_identifier" | "scoped_type_identifier"
5053    ) || !qualified_type_scope_contains_template(node)
5054    {
5055        return None;
5056    }
5057    let name = node
5058        .child_by_field_name("name")
5059        .filter(|name| name.kind() == "type_identifier")?;
5060    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5061        return None;
5062    }
5063    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5064    if !owner_target.is_class() {
5065        return None;
5066    }
5067    let owner = node.child_by_field_name("scope")?;
5068    let owner_resolution = resolve_type_node_lexically_for_target(
5069        owner,
5070        &ctx.analyzer,
5071        ctx.visibility,
5072        &ctx.ordinary_type_imports,
5073        ctx.file,
5074        ctx.source,
5075        &owner_target,
5076        Some(&ctx.lexical_scope_cache),
5077        ctx.recovered_sentinel_scope(owner).as_deref(),
5078    );
5079    if matches!(
5080        owner_resolution,
5081        LexicalTypeResolution::Resolved {
5082            ref unit,
5083            ref candidates,
5084            ..
5085        } if type_resolution_matches_unit_target(
5086            owner,
5087            unit,
5088            candidates,
5089            &owner_target,
5090            ctx,
5091        )
5092    ) {
5093        return Some(name);
5094    }
5095
5096    let qualified = qualified_owner_components(node, ctx.source)?;
5097    let namespace = ctx
5098        .lexical_scope_cache
5099        .orphaned
5100        .enclosing_namespace_components(node, ctx.source);
5101    let indexed_scope = ctx
5102        .recovered_sentinel_scope(node)
5103        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5104        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5105    let owner_components = canonical_cpp_scope_components(&owner_target);
5106    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5107        .any(|components| components == owner_components)
5108    {
5109        return None;
5110    }
5111    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5112        .into_iter()
5113        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5114        .collect::<Vec<_>>();
5115    (!scoped_candidates.is_empty()
5116        && scoped_candidates
5117            .iter()
5118            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5119    .then_some(name)
5120}
5121
5122/// Recover a nested type-alias reference when parser recovery leaves an
5123/// unqualified template argument under a member function.  The ordinary
5124/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5125/// even though the indexed callable owner proves that the reference is inside
5126/// the class which declares the target alias.
5127fn target_guided_missing_member_alias_type_leaf<'tree>(
5128    node: Node<'tree>,
5129    ctx: &ScanCtx<'_>,
5130) -> Option<Node<'tree>> {
5131    if !is_cpp_template_argument_type_leaf(node)
5132        || is_declaration_name(node)
5133        || ctx
5134            .target_declaration_ranges
5135            .iter()
5136            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5137        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5138        || local_type_name_shadows(node, ctx)
5139        || !ctx
5140            .analyzer
5141            .type_alias_provider()
5142            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5143    {
5144        return None;
5145    }
5146    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5147    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5148    if !owner_scope_matches
5149        && !indexed_scope.is_some_and(|scope| {
5150            indexed_scope_matches_target_name(
5151                &scope,
5152                &[ctx.spec.target.identifier().to_string()],
5153                false,
5154                &ctx.spec.target,
5155            )
5156        })
5157    {
5158        return None;
5159    }
5160    // The indexed symbol table intentionally retains declarations from every
5161    // preprocessor branch and from later source positions.  The recovered
5162    // class-owner scope proves the spelling, but it does not prove that this
5163    // alias was active and introduced before the reference.  Apply the same
5164    // structured guard/source-order check used by the ordinary resolver before
5165    // turning the target-guided recovery into a proven hit.
5166    if !(ctx.visibility.external_type_candidate_visible_in_context(
5167        &ctx.analyzer,
5168        ctx.file,
5169        &ctx.spec.target,
5170        node,
5171    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5172    {
5173        return None;
5174    }
5175    let target_visible = ctx
5176        .visibility
5177        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5178        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5179    target_visible.then_some(node)
5180}
5181
5182/// Recover a class/enum template argument only when the parser's ordinary
5183/// lexical lookup failed but the indexed scope and visible declaration still
5184/// prove the exact target. This intentionally excludes aliases: an alias
5185/// argument needs its own template-argument selection path, while a direct
5186/// class/enum argument can be identified by its canonical scope and symbol.
5187fn target_guided_missing_template_argument_type_leaf<'tree>(
5188    node: Node<'tree>,
5189    ctx: &ScanCtx<'_>,
5190) -> Option<Node<'tree>> {
5191    let target = &ctx.spec.target;
5192    let name = node_text(node, ctx.source);
5193    if !target.is_class()
5194        || !is_cpp_template_argument_type_leaf(node)
5195        || is_declaration_name(node)
5196        || name != target.identifier()
5197        || ctx.local_shadows.is_shadowed(name)
5198        || local_type_name_shadows(node, ctx)
5199        || !ctx.visibility.is_physically_visible(ctx.file, target)
5200        || ctx
5201            .analyzer
5202            .type_alias_provider()
5203            .is_some_and(|provider| provider.is_type_alias(target))
5204    {
5205        return None;
5206    }
5207
5208    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5209    let target_components = canonical_cpp_scope_components(target);
5210    if target_components.last().map(String::as_str) != Some(name)
5211        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5212            .any(|components| components == target_components)
5213    {
5214        return None;
5215    }
5216
5217    // The direct visible class candidate supplies the declaration identity;
5218    // the scope check above supplies its canonical owner path. Do not let an
5219    // alias or a same-scoped competing class enter this recovery path.
5220    let candidates = visible_type_identifier_candidates(ctx, name);
5221    if candidates.is_empty()
5222        || candidates.iter().any(|candidate| {
5223            !candidate.is_class()
5224                || ctx
5225                    .analyzer
5226                    .type_alias_provider()
5227                    .is_some_and(|provider| provider.is_type_alias(candidate))
5228                || (!same_visible_symbol(candidate, target)
5229                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5230                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5231        })
5232        || !candidates
5233            .iter()
5234            .any(|candidate| same_visible_symbol(candidate, target))
5235    {
5236        return None;
5237    }
5238
5239    // Physical visibility covers the file/import projection; this second
5240    // guard preserves declaration ordering and preprocessor branch identity.
5241    ctx.visibility
5242        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5243        .then_some(node)
5244}
5245
5246/// Recover the class owner of an out-of-line member whose trailing attribute
5247/// macro was parsed as a separate function definition around the real body.
5248fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5249    let mut function = node;
5250    while function.kind() != "function_definition" {
5251        function = ctx.ancestry.parent(function)?;
5252    }
5253    let macro_name = function_definition_name_node(function)?;
5254    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5255        return None;
5256    }
5257
5258    // An unknown trailing attribute macro can split one real definition into
5259    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5260    // macro-named function definition that owns the body. Recover only that
5261    // exact CST sequence; a complete declaration or a non-macro function is
5262    // an ordinary independent construct.
5263    let declaration = function.prev_named_sibling()?;
5264    if declaration.kind() != "declaration"
5265        || !declaration.has_error()
5266        || function.start_position().row > declaration.end_position().row + 1
5267    {
5268        return None;
5269    }
5270    let mut missing_semicolon = false;
5271    let mut real_semicolon = false;
5272    for child in children_iter(declaration) {
5273        if child.kind() == ";" {
5274            missing_semicolon |= child.is_missing();
5275            real_semicolon |= !child.is_missing();
5276        }
5277    }
5278    if !missing_semicolon || real_semicolon {
5279        return None;
5280    }
5281
5282    let initializer = declaration.child_by_field_name("declarator")?;
5283    if initializer.kind() != "init_declarator"
5284        || initializer
5285            .child_by_field_name("value")
5286            .is_none_or(|value| value.kind() != "argument_list")
5287    {
5288        return None;
5289    }
5290    let qualified_name = initializer
5291        .child_by_field_name("declarator")
5292        .and_then(declarator_name_node)?;
5293    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5294    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5295    match ctx.visibility.resolve_type_components_lexically(
5296        &ctx.analyzer,
5297        ctx.file,
5298        &qualified.names,
5299        qualified.global,
5300        &lexical_scope,
5301    ) {
5302        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5303        LexicalTypeResolution::Resolved { .. }
5304        | LexicalTypeResolution::Ambiguous
5305        | LexicalTypeResolution::Missing => None,
5306    }
5307}
5308
5309/// A class member alias is visible throughout its complete class scope, even
5310/// when its declaration byte follows a recovered out-of-line member's
5311/// trailing return type. Match the indexed owner path structurally before
5312/// allowing the guard-only visibility check above to waive source ordering.
5313fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5314    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5315}
5316
5317fn member_alias_owner_matches_reference_for(
5318    target: &CodeUnit,
5319    node: Node<'_>,
5320    ctx: &ScanCtx<'_>,
5321) -> bool {
5322    let Some(owner) = ctx.analyzer.parent_of(target) else {
5323        return false;
5324    };
5325    if !owner.is_class() {
5326        return false;
5327    }
5328    let reference_owner = ctx
5329        .class_ranges
5330        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5331        .or_else(|| structured_enclosing_owner(node, ctx));
5332    if reference_owner.as_ref().is_some_and(|reference_owner| {
5333        ctx.visibility
5334            .same_template_owner_identity(&owner, reference_owner)
5335    }) {
5336        return true;
5337    }
5338    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5339        ctx.visibility
5340            .same_template_owner_identity(&owner, &reference_owner)
5341    }) {
5342        return true;
5343    }
5344    if reference_owner.is_some_and(|reference_owner| {
5345        matches!(
5346            resolve_declaring_member_owner(
5347                &ctx.analyzer,
5348                ctx.visibility,
5349                ctx.file,
5350                &reference_owner,
5351                target.identifier(),
5352            ),
5353            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5354                if ctx
5355                    .visibility
5356                    .same_template_owner_identity(&owner, &declaring_owner)
5357        )
5358    }) {
5359        return true;
5360    }
5361    let range = Range {
5362        start_byte: node.start_byte(),
5363        end_byte: node.end_byte(),
5364        start_line: node.start_position().row + 1,
5365        end_line: node.end_position().row + 1,
5366    };
5367    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5368    while let Some(candidate) = indexed_enclosing {
5369        if candidate.is_class()
5370            && ctx
5371                .visibility
5372                .same_template_owner_identity(&owner, &candidate)
5373        {
5374            return true;
5375        }
5376        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5377    }
5378    if let Some(reference_body) = malformed_recovered_class_body(node) {
5379        let mut root = node;
5380        while let Some(parent) = ctx.ancestry.parent(root) {
5381            root = parent;
5382        }
5383        if ctx.analyzer.ranges(target).iter().any(|range| {
5384            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5385                .and_then(malformed_recovered_class_body)
5386                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5387        }) {
5388            return true;
5389        }
5390    }
5391    if structured_enclosing_owner(node, ctx)
5392        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5393    {
5394        return true;
5395    }
5396    let owner_components = canonical_cpp_scope_components(&owner);
5397    if ctx
5398        .recovered_sentinel_scope(node)
5399        .is_some_and(|scope| scope == owner_components)
5400    {
5401        return true;
5402    }
5403    if matches!(
5404        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5405            node,
5406            &ctx.analyzer,
5407            ctx.visibility,
5408            ctx.file,
5409            ctx.source,
5410            false,
5411            false,
5412            Some(&ctx.lexical_scope_cache),
5413        ),
5414        LexicalScopeResolution::Resolved(reference_scope)
5415            if reference_scope == owner_components
5416    ) {
5417        return true;
5418    }
5419    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5420    else {
5421        return false;
5422    };
5423    !owner_components.is_empty() && reference_scope == owner_components
5424}
5425
5426fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5427    loop {
5428        if node.kind() == "compound_statement"
5429            && node
5430                .parent()
5431                .is_some_and(|parent| parent.kind() == "declaration_list")
5432            && node.prev_named_sibling().is_some_and(|header| {
5433                header.kind() == "ERROR"
5434                    && header.end_byte() <= node.start_byte()
5435                    && error_contains_class_header(header)
5436            })
5437        {
5438            return Some(node);
5439        }
5440        node = node.parent()?;
5441    }
5442}
5443
5444fn error_contains_class_header(node: Node<'_>) -> bool {
5445    let mut pending = vec![(node, 0usize)];
5446    while let Some((current, depth)) = pending.pop() {
5447        if matches!(current.kind(), "class" | "struct" | "union") {
5448            let mut sibling = current.next_sibling();
5449            let mut saw_name = false;
5450            while let Some(candidate) = sibling {
5451                match candidate.kind() {
5452                    "comment" => {}
5453                    "{" | "base_class_clause" | ":" => return saw_name,
5454                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5455                    _ if !candidate.is_named() => {}
5456                    _ => break,
5457                }
5458                sibling = candidate.next_sibling();
5459            }
5460        }
5461        if depth >= 1 {
5462            continue;
5463        }
5464        let mut cursor = current.walk();
5465        pending.extend(
5466            current
5467                .children(&mut cursor)
5468                .filter(|child| child.kind() != "compound_statement")
5469                .map(|child| (child, depth + 1)),
5470        );
5471    }
5472    false
5473}
5474
5475fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5476    has_ancestor_kind(node, "compound_statement")
5477        && ctx
5478            .visibility
5479            .external_type_candidate_guard_compatible_in_context(
5480                &ctx.analyzer,
5481                ctx.file,
5482                &ctx.spec.target,
5483                node,
5484            )
5485}
5486
5487fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5488    ctx.analyzer
5489        .type_alias_provider()
5490        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5491        && member_alias_owner_matches_reference(node, ctx)
5492}
5493
5494/// A nested class can use aliases declared by any enclosing class. Preserve
5495/// that structured owner chain for malformed macro-return nodes, whose phantom
5496/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5497fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5498    if !ctx
5499        .analyzer
5500        .type_alias_provider()
5501        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5502    {
5503        return false;
5504    }
5505    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5506        return false;
5507    };
5508    let mut reference_owner = structured_enclosing_owner(node, ctx);
5509    while let Some(owner) = reference_owner {
5510        if same_logical_symbol(&target_owner, &owner) {
5511            return true;
5512        }
5513        reference_owner = ctx.analyzer.parent_of(&owner);
5514    }
5515    false
5516}
5517
5518/// An enclosing class alias is only usable when no nearer class declares the
5519/// same type name. The recovered macro-return path does not have a complete
5520/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5521/// rule before the enclosing alias fast path runs.
5522fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5523    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5524        return false;
5525    };
5526    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5527        return false;
5528    };
5529    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5530        return false;
5531    };
5532    let candidates = ctx
5533        .visibility
5534        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5535        .filter(|candidate| {
5536            candidate.is_class()
5537                && alias_provider.is_type_alias(candidate)
5538                && !same_visible_symbol(candidate, &ctx.spec.target)
5539        })
5540        .cloned()
5541        .collect::<Vec<_>>();
5542
5543    let mut owner = Some(reference_owner);
5544    while let Some(owner_unit) = owner {
5545        if same_logical_symbol(&target_owner, &owner_unit) {
5546            return false;
5547        }
5548        if candidates.iter().any(|candidate| {
5549            ctx.analyzer
5550                .parent_of(candidate)
5551                .is_some_and(|candidate_owner| {
5552                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5553                })
5554                && ctx
5555                    .visibility
5556                    .external_type_candidate_guard_compatible_in_context(
5557                        &ctx.analyzer,
5558                        ctx.file,
5559                        candidate,
5560                        node,
5561                    )
5562        }) {
5563            return true;
5564        }
5565        owner = ctx.analyzer.parent_of(&owner_unit);
5566    }
5567    false
5568}
5569
5570fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5571    if cpp_active_template_type_parameter(
5572        node,
5573        ctx.spec.target.identifier(),
5574        ctx.source,
5575        &ctx.ancestry,
5576    ) {
5577        return true;
5578    }
5579    let Some(callable) = nearest_callable_scope(node) else {
5580        return false;
5581    };
5582    let mut root_callable = callable;
5583    let mut ancestor = ctx.ancestry.parent(callable);
5584    while let Some(current) = ancestor {
5585        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5586            root_callable = current;
5587        }
5588        ancestor = ctx.ancestry.parent(current);
5589    }
5590
5591    let mut stack = vec![root_callable];
5592    while let Some(current) = stack.pop() {
5593        if current.start_byte() >= node.start_byte() {
5594            continue;
5595        }
5596        if let Some(name) = local_type_name_declaration_node(current)
5597            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5598            && nearest_callable_scope(current).is_some_and(|owner| {
5599                !is_malformed_wrapper_function_definition(owner)
5600                    && owner.start_byte() <= callable.start_byte()
5601                    && callable.end_byte() <= owner.end_byte()
5602            })
5603            && local_alias_scope_contains_node(current, node)
5604        {
5605            return true;
5606        }
5607        let mut cursor = current.walk();
5608        stack.extend(current.named_children(&mut cursor));
5609    }
5610    false
5611}
5612
5613fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5614    local_type_alias_name_node(node).or_else(|| match node.kind() {
5615        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5616            .child_by_field_name("name")
5617            .filter(|name| is_declaration_name(*name)),
5618        _ => None,
5619    })
5620}
5621
5622fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5623    loop {
5624        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5625            return Some(node);
5626        }
5627        node = node.parent()?;
5628    }
5629}
5630
5631fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5632    match node.kind() {
5633        "alias_declaration" => node.child_by_field_name("name"),
5634        "type_definition" => node
5635            .child_by_field_name("declarator")
5636            .and_then(declarator_name_node),
5637        _ => None,
5638    }
5639}
5640
5641fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5642    let mut current = alias.parent();
5643    while let Some(parent) = current {
5644        if matches!(
5645            parent.kind(),
5646            "class_specifier" | "struct_specifier" | "union_specifier"
5647        ) {
5648            return false;
5649        }
5650        if parent.kind() == "compound_statement" {
5651            return parent.start_byte() <= node.start_byte()
5652                && node.end_byte() <= parent.end_byte();
5653        }
5654        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5655            let Some(body) = parent.child_by_field_name("body") else {
5656                return false;
5657            };
5658            return node_is_within(body, alias) && node_is_within(body, node);
5659        }
5660        current = parent.parent();
5661    }
5662    false
5663}
5664
5665fn target_guided_missing_declaration_type_leaf<'tree>(
5666    node: Node<'tree>,
5667    ctx: &ScanCtx<'_>,
5668) -> Option<Node<'tree>> {
5669    if is_declaration_name(node) {
5670        return None;
5671    }
5672    let component_nodes = cpp_name_component_nodes(node)?;
5673    let name_node = component_nodes.last().copied()?;
5674    let name = node_text(name_node, ctx.source);
5675    if name != ctx.spec.target.identifier() {
5676        return None;
5677    }
5678    let inside_target_declaration = ctx
5679        .target_declaration_ranges
5680        .iter()
5681        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5682    if !inside_target_declaration
5683        && !ctx.visibility.external_type_candidate_visible_in_context(
5684            &ctx.analyzer,
5685            ctx.file,
5686            &ctx.spec.target,
5687            node,
5688        )
5689    {
5690        return None;
5691    }
5692    let components = component_nodes
5693        .iter()
5694        .map(|component| node_text(*component, ctx.source).to_string())
5695        .collect::<Vec<_>>();
5696    let local_alias_shadow = local_type_name_shadows(node, ctx);
5697    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5698    let indexed_alias_owner = ctx
5699        .analyzer
5700        .type_alias_provider()
5701        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5702        && member_alias_owner_matches_reference(node, ctx);
5703    let target_alias_self_reference = inside_target_declaration
5704        && ctx
5705            .analyzer
5706            .type_alias_provider()
5707            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5708    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5709        &ctx.analyzer,
5710        ctx.file,
5711        &ctx.spec.target,
5712        node,
5713    ) || member_alias_complete_class_context(node, ctx);
5714    if !target_alias_self_reference
5715        && !local_alias_shadow
5716        && member_alias_visible
5717        && (structured_alias_owner || indexed_alias_owner)
5718    {
5719        return Some(node);
5720    }
5721    let declaration = nearest_declaration_type_context(node)?;
5722    let candidates = visible_type_identifier_candidates(ctx, name);
5723    let unique_visible_target = !candidates.is_empty()
5724        && candidates
5725            .iter()
5726            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5727    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5728    let exact_scope_match = indexed_scope_matches_target_name(
5729        &indexed_scope,
5730        &components,
5731        is_globally_qualified_cpp_name(node),
5732        &ctx.spec.target,
5733    );
5734    if matches!(declaration.kind(), "field_declaration" | "declaration") {
5735        let parser_lost_declaration_scope =
5736            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5737                && unique_visible_target;
5738        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5739    }
5740    let lost_namespace_parameter_context =
5741        matches!(
5742            declaration.kind(),
5743            "parameter_declaration" | "optional_parameter_declaration"
5744        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5745    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5746        return Some(node);
5747    }
5748    None
5749}
5750
5751fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5752    node: Node<'tree>,
5753    ctx: &ScanCtx<'_>,
5754) -> Option<Node<'tree>> {
5755    let mut stack = vec![node];
5756    while let Some(candidate) = stack.pop() {
5757        if candidate.kind() == "type_identifier"
5758            && !is_declaration_name(candidate)
5759            && matches!(
5760                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
5761                Some("template_type")
5762            )
5763        {
5764            let mut current = ctx.ancestry.parent(candidate);
5765            let mut saw_qualified = false;
5766            let mut saw_dependent = false;
5767            let mut saw_type_descriptor = false;
5768            let mut saw_alias_declaration = false;
5769            while let Some(ancestor) = current {
5770                match ancestor.kind() {
5771                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5772                    "dependent_type" => saw_dependent = true,
5773                    "type_descriptor" => saw_type_descriptor = true,
5774                    "alias_declaration" => {
5775                        saw_alias_declaration = true;
5776                        break;
5777                    }
5778                    "template_type"
5779                    | "template_argument_list"
5780                    | "typename"
5781                    | "template_declaration" => {}
5782                    _ => {}
5783                }
5784                current = ctx.ancestry.parent(ancestor);
5785            }
5786            let name = node_text(candidate, ctx.source);
5787            let visible_candidates = visible_type_identifier_candidates(ctx, name);
5788            let canonical_alias_target = visible_candidates
5789                .iter()
5790                .filter_map(|alias| ctx.visibility.alias_target(alias))
5791                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5792            let alias_resolves =
5793                ctx.visibility
5794                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
5795                    || canonical_alias_target;
5796            if saw_qualified
5797                && saw_dependent
5798                && saw_type_descriptor
5799                && saw_alias_declaration
5800                && alias_resolves
5801                && ctx.visibility.external_type_candidate_visible_in_context(
5802                    &ctx.analyzer,
5803                    ctx.file,
5804                    &ctx.spec.target,
5805                    candidate,
5806                )
5807            {
5808                return Some(candidate);
5809            }
5810        }
5811        push_named_children_reversed(candidate, &mut stack);
5812    }
5813    None
5814}
5815
5816fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5817    let mut current = Some(node);
5818    while let Some(ancestor) = current {
5819        if matches!(
5820            ancestor.kind(),
5821            "field_declaration"
5822                | "parameter_declaration"
5823                | "optional_parameter_declaration"
5824                | "declaration"
5825                | "type_descriptor"
5826        ) {
5827            let contains_type = ancestor
5828                .child_by_field_name("type")
5829                .is_some_and(|type_node| {
5830                    type_node.start_byte() <= node.start_byte()
5831                        && node.end_byte() <= type_node.end_byte()
5832                });
5833            if contains_type
5834                && !(ancestor.kind() == "type_descriptor"
5835                    && is_cpp_template_argument_type_leaf(node))
5836            {
5837                return Some(ancestor);
5838            }
5839            if ancestor.kind() == "type_descriptor"
5840                && ancestor.parent().is_some_and(|parent| {
5841                    matches!(
5842                        parent.kind(),
5843                        "cast_expression"
5844                            | "new_expression"
5845                            | "sizeof_expression"
5846                            | "alignof_expression"
5847                            | "typeid_expression"
5848                    )
5849                })
5850            {
5851                return Some(ancestor);
5852            }
5853        }
5854        if matches!(
5855            ancestor.kind(),
5856            "compound_statement"
5857                | "translation_unit"
5858                | "namespace_definition"
5859                | "alias_declaration"
5860                | "type_definition"
5861                | "base_class_clause"
5862        ) {
5863            return None;
5864        }
5865        current = ancestor.parent();
5866    }
5867    None
5868}
5869
5870fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5871    let mut candidates = Vec::new();
5872    for candidate in ctx
5873        .visibility
5874        .visible_identifier_candidates(ctx.file, name)
5875        .filter(|candidate| {
5876            candidate.is_class()
5877                || ctx
5878                    .analyzer
5879                    .type_alias_provider()
5880                    .is_some_and(|provider| provider.is_type_alias(candidate))
5881        })
5882    {
5883        if !candidates
5884            .iter()
5885            .any(|existing| same_logical_symbol(existing, candidate))
5886        {
5887            candidates.push(candidate.clone());
5888        }
5889    }
5890    candidates
5891}
5892
5893/// Recover a direct type-alias argument of `static_cast` when parser recovery
5894/// misclassifies a namespace alias as a local declaration. The indexed scope
5895/// and exact alias identity are required so a same-spelled alias in another
5896/// namespace remains excluded.
5897fn target_guided_static_cast_alias_type_descriptor<'tree>(
5898    node: Node<'tree>,
5899    ctx: &ScanCtx<'_>,
5900) -> Option<Node<'tree>> {
5901    if node.kind() != "type_descriptor" {
5902        return None;
5903    }
5904    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
5905        parent.kind() == "template_argument_list"
5906            && parent.named_child_count() == 1
5907            && parent.named_child(0) == Some(node)
5908    })?;
5909    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
5910        parent.kind() == "template_function"
5911            && parent.child_by_field_name("arguments") == Some(argument_list)
5912    })?;
5913    let name = template.child_by_field_name("name")?;
5914    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5915        return None;
5916    }
5917    let target = &ctx.spec.target;
5918    if node_text(node, ctx.source) != target.identifier()
5919        || !ctx
5920            .analyzer
5921            .type_alias_provider()
5922            .is_some_and(|provider| provider.is_type_alias(target))
5923        || !ctx.visibility.is_physically_visible(ctx.file, target)
5924        || !ctx.visibility.external_type_candidate_visible_in_context(
5925            &ctx.analyzer,
5926            ctx.file,
5927            target,
5928            node,
5929        )
5930    {
5931        return None;
5932    }
5933
5934    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5935    let target_scope = canonical_cpp_scope_components(target);
5936    let name_components = [target.identifier().to_string()];
5937    if !lexical_component_tiers(&name_components, false, &indexed_scope)
5938        .any(|components| components == target_scope)
5939    {
5940        return None;
5941    }
5942
5943    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5944    if !candidates
5945        .iter()
5946        .any(|candidate| same_visible_symbol(candidate, target))
5947    {
5948        return None;
5949    }
5950    if candidates.iter().any(|candidate| {
5951        !same_visible_symbol(candidate, target)
5952            && lexical_component_tiers(&name_components, false, &indexed_scope)
5953                .any(|components| components == canonical_cpp_scope_components(candidate))
5954    }) {
5955        return None;
5956    }
5957    Some(node)
5958}
5959
5960fn indexed_scope_matches_target_name(
5961    indexed_scope: &[String],
5962    components: &[String],
5963    global: bool,
5964    target: &CodeUnit,
5965) -> bool {
5966    let target_name = cpp_name_for(target);
5967    lexical_component_tiers(components, global, indexed_scope)
5968        .any(|qualified| qualified.join("::") == target_name)
5969}
5970
5971fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5972    if target.package_name().is_empty() {
5973        return false;
5974    }
5975    if indexed_scope.len() <= 1 {
5976        return true;
5977    }
5978    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5979        brokk_bifrost_core::analyzer::Language::Cpp,
5980        &cpp_name_for(target),
5981    );
5982    target_scope.pop();
5983    (1..indexed_scope.len())
5984        .rev()
5985        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5986}
5987
5988fn indexed_enclosing_lexical_scope(
5989    analyzer: &CppGraphSource<'_>,
5990    file: &ProjectFile,
5991    node: Node<'_>,
5992) -> Option<Vec<String>> {
5993    let range = Range {
5994        start_byte: node.start_byte(),
5995        end_byte: node.end_byte(),
5996        start_line: node.start_position().row,
5997        end_line: node.end_position().row,
5998    };
5999    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6000    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6001        brokk_bifrost_core::analyzer::Language::Cpp,
6002        &cpp_name_for(&enclosing),
6003    );
6004    if !enclosing.is_class() && !enclosing.is_module() {
6005        components.pop();
6006    }
6007    Some(components)
6008}
6009
6010fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6011    if node.kind() != "qualified_identifier" {
6012        return None;
6013    }
6014    let mut stack = vec![node];
6015    while let Some(current) = stack.pop() {
6016        if current.kind() != "qualified_identifier" {
6017            continue;
6018        }
6019        if let Some(scope) = current.child_by_field_name("scope") {
6020            let text = qualified_scope_text(scope, ctx.source);
6021            if name_mentions(&text, &ctx.spec.member_name) {
6022                return Some(scope);
6023            }
6024        }
6025        let mut cursor = current.walk();
6026        for child in current.named_children(&mut cursor) {
6027            if child.kind() == "qualified_identifier" {
6028                stack.push(child);
6029            }
6030        }
6031    }
6032    None
6033}
6034
6035fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6036    let mut parts = vec![node_text(scope, source).to_string()];
6037    let mut current = scope.parent();
6038    while let Some(qualified) = current {
6039        let Some(parent) = qualified.parent() else {
6040            break;
6041        };
6042        if parent.kind() != "qualified_identifier"
6043            || parent.child_by_field_name("name") != Some(qualified)
6044        {
6045            break;
6046        }
6047        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6048            parts.push(node_text(outer_scope, source).to_string());
6049        }
6050        current = Some(parent);
6051    }
6052    parts.reverse();
6053    parts.join("::")
6054}
6055
6056fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6057    if node.kind() == "using_declaration" {
6058        maybe_record_using_callable_hit(node, ctx);
6059        return;
6060    }
6061    if has_ancestor_kind(node, "using_declaration") {
6062        return;
6063    }
6064    if node.kind() == "function_definition" {
6065        return;
6066    }
6067    if !matches!(
6068        node.kind(),
6069        "call_expression"
6070            | "new_expression"
6071            | "compound_literal_expression"
6072            | "declaration"
6073            | "field_initializer"
6074    ) {
6075        return;
6076    }
6077    let Some(owner) = ctx.spec.owner.as_ref() else {
6078        return;
6079    };
6080    if node.kind() == "field_initializer" {
6081        if !field_initializer_constructs_target(node, ctx, owner)
6082            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6083        {
6084            return;
6085        }
6086        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6087            match ctx
6088                .visibility
6089                .call_arity_evidence(ctx.file, node, ctx.source)
6090                .accepts(expected)
6091            {
6092                Some(true) => {}
6093                Some(false) => return,
6094                None => {
6095                    push_unproven_hit(node, ctx);
6096                    return;
6097                }
6098            }
6099        }
6100        match constructor_overload_selection(node, ctx) {
6101            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6102            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6103            ConstructorOverloadSelection::OtherOverload => {}
6104        }
6105        return;
6106    }
6107    if node.kind() == "declaration" {
6108        if declaration_is_object_construction_candidate(node, ctx)
6109            && declaration_mentions_type(node, ctx, owner)
6110            && ctx
6111                .spec
6112                .callable_arity_at(node.start_byte())
6113                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6114        {
6115            match constructor_overload_selection(node, ctx) {
6116                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6117                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6118                ConstructorOverloadSelection::OtherOverload => {}
6119            }
6120        }
6121        return;
6122    }
6123    let Some(type_node) = constructor_type_node(node) else {
6124        return;
6125    };
6126    let hit_node = function_terminal_node(type_node);
6127    let text = node_text(type_node, ctx.source);
6128    if !name_mentions(text, &ctx.spec.member_name) {
6129        return;
6130    }
6131    *ctx.raw_match_count += 1;
6132    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6133        match ctx
6134            .visibility
6135            .call_arity_evidence(ctx.file, node, ctx.source)
6136            .accepts(expected)
6137        {
6138            Some(true) => {}
6139            Some(false) => return,
6140            None => {
6141                push_unproven_hit(hit_node, ctx);
6142                return;
6143            }
6144        }
6145    }
6146    match constructor_overload_selection(node, ctx) {
6147        ConstructorOverloadSelection::Target => {}
6148        ConstructorOverloadSelection::Ambiguous => {
6149            push_unproven_hit(hit_node, ctx);
6150            return;
6151        }
6152        ConstructorOverloadSelection::OtherOverload => return,
6153    }
6154    let structured_resolution = resolve_type_node_lexically_for_target(
6155        type_node,
6156        &ctx.analyzer,
6157        ctx.visibility,
6158        &ctx.ordinary_type_imports,
6159        ctx.file,
6160        ctx.source,
6161        owner,
6162        Some(&ctx.lexical_scope_cache),
6163        ctx.recovered_sentinel_scope(type_node).as_deref(),
6164    );
6165    let structurally_resolves = matches!(
6166        &structured_resolution,
6167        LexicalTypeResolution::Resolved {
6168            unit, candidates, ..
6169        } if same_visible_symbol(unit, owner)
6170            || candidates
6171                .iter()
6172                .any(|candidate| same_visible_symbol(candidate, owner))
6173    );
6174    if structurally_resolves
6175        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6176            && ctx
6177                .visibility
6178                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6179    {
6180        push_hit(hit_node, ctx);
6181    } else {
6182        push_unproven_hit(hit_node, ctx);
6183    }
6184}
6185
6186/// What the overload filter says about a construction site relative to the
6187/// constructor being scanned.
6188enum ConstructorOverloadSelection {
6189    /// The arguments leave the scan target as the only viable constructor, or
6190    /// the filter has no evidence to apply here. Either way nothing about the
6191    /// arguments argues against the site, so the rest of the scan decides it.
6192    Target,
6193    /// More than one of the owner's constructors stays viable, so the site
6194    /// cannot be proven to belong to the target.
6195    Ambiguous,
6196    /// The arguments select a sibling constructor and not the target.
6197    OtherOverload,
6198}
6199
6200/// Which of the owner's constructors the arguments at a construction site
6201/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6202/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6203/// `T var(args)` declaration.
6204///
6205/// This is the inverse reading of the forward direction's overload choice. The
6206/// candidate set is the owner's arity-compatible constructors, the arguments are
6207/// typed by the same `expression_arg_type` the method and free-function scan
6208/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6209/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6210/// arity alone made a scan of one overload claim every same-arity sibling's call
6211/// (#2908).
6212fn constructor_overload_selection(
6213    node: Node<'_>,
6214    ctx: &ScanCtx<'_>,
6215) -> ConstructorOverloadSelection {
6216    let Some(owner) = ctx.spec.owner.as_ref() else {
6217        return ConstructorOverloadSelection::Target;
6218    };
6219    if ctx.spec.param_types.is_none() {
6220        return ConstructorOverloadSelection::Target;
6221    }
6222    // A `T var(args)` declaration keeps its arguments under the declarator or the
6223    // init declarator's value; every other shape spells an argument list that the
6224    // shared call helpers find, and its argument count needs the macro-aware
6225    // arity evidence.
6226    let (arity, arg_types) = if node.kind() == "declaration" {
6227        match declaration_constructor_initializer(node) {
6228            DeclarationConstructorInitializer::Arguments(arguments) => (
6229                argument_children(arguments).count(),
6230                argument_list_types(arguments, ctx),
6231            ),
6232            DeclarationConstructorInitializer::Expression(value) => {
6233                (1, vec![expression_arg_type(value, ctx)])
6234            }
6235            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6236        }
6237    } else {
6238        let Some(arity) = ctx
6239            .visibility
6240            .call_arity_evidence(ctx.file, node, ctx.source)
6241            .exact()
6242        else {
6243            return ConstructorOverloadSelection::Target;
6244        };
6245        (arity, call_argument_types(node, ctx))
6246    };
6247    let mut candidates = ctx
6248        .visibility
6249        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6250        .into_iter()
6251        .filter(|unit| unit.is_function())
6252        .cloned()
6253        .collect::<Vec<_>>();
6254    candidates.retain(|unit| {
6255        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6256    });
6257    if !candidates
6258        .iter()
6259        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6260    {
6261        return ConstructorOverloadSelection::Target;
6262    }
6263    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6264        candidates,
6265        &arg_types,
6266        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6267        &|candidate| {
6268            ctx.visibility
6269                .callable_is_template_declaration(&ctx.analyzer, candidate)
6270        },
6271        &|name| ctx.visibility.resolve_type(ctx.file, name),
6272        &|left, right| same_visible_symbol(left, right),
6273    );
6274    if !filtered
6275        .iter()
6276        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6277    {
6278        return ConstructorOverloadSelection::OtherOverload;
6279    }
6280    // A header declaration and its out-of-line body are one constructor even
6281    // when their persisted signature strings spell a parameter differently, so
6282    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6283    if filtered.iter().all(|candidate| {
6284        ctx.visibility
6285            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6286    }) {
6287        ConstructorOverloadSelection::Target
6288    } else {
6289        ConstructorOverloadSelection::Ambiguous
6290    }
6291}
6292
6293fn unqualified_base_initializer_constructs_target(
6294    node: Node<'_>,
6295    ctx: &ScanCtx<'_>,
6296    target_owner: &CodeUnit,
6297) -> bool {
6298    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6299        return false;
6300    }
6301    let Some(name) = node
6302        .child_by_field_name("name")
6303        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6304    else {
6305        return false;
6306    };
6307    if node_text(name, ctx.source) != ctx.spec.member_name {
6308        return false;
6309    }
6310    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6311        return false;
6312    };
6313    let inherited = ctx.visibility.inherited_injected_class_owner(
6314        &ctx.analyzer,
6315        ctx.file,
6316        &enclosing_owner,
6317        &ctx.spec.member_name,
6318    );
6319    inherited.is_some_and(|owner| {
6320        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6321    })
6322}
6323
6324fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6325    if node.kind() == "function_definition" {
6326        maybe_record_free_function_definition_hit(node, ctx);
6327        return;
6328    }
6329    if node.kind() == "function_declarator" {
6330        maybe_record_recovered_error_free_function_call(node, ctx);
6331        return;
6332    }
6333    if node.kind() == "identifier" {
6334        maybe_record_free_function_value_reference(node, ctx);
6335        return;
6336    }
6337    if node.kind() != "call_expression" {
6338        return;
6339    }
6340    let Some(function) = node
6341        .child_by_field_name("function")
6342        .or_else(|| node.named_child(0))
6343    else {
6344        return;
6345    };
6346    let text = node_text(function, ctx.source);
6347    if !name_matches_callable(text, &ctx.spec.member_name) {
6348        return;
6349    }
6350    *ctx.raw_match_count += 1;
6351    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6352        match ctx
6353            .visibility
6354            .call_arity_evidence(ctx.file, node, ctx.source)
6355            .accepts(expected)
6356        {
6357            Some(true) => {}
6358            Some(false) => return,
6359            None => {
6360                // The argument count is unknown after macro expansion. It still
6361                // cannot select a *different* target when the bare name binds
6362                // to exactly one visible callable, so let the bare-call
6363                // resolution below prove that site; every other shape stays
6364                // unproven (#1811, the scan side of the same over-conservatism
6365                // that made the forward answer discard its lone candidate).
6366                if !bare_name_binds_only_target(node, function, text, ctx) {
6367                    push_unproven_hit(function_terminal_node(function), ctx);
6368                    return;
6369                }
6370            }
6371        }
6372    }
6373    if matches!(function.kind(), "identifier" | "template_function") {
6374        let terminal = function_terminal_node(function);
6375        let name = node_text(terminal, ctx.source);
6376        if ctx.local_shadows.is_shadowed(name) {
6377            return;
6378        }
6379        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6380            && !matches!(
6381                resolve_declaring_member_owner(
6382                    &ctx.analyzer,
6383                    ctx.visibility,
6384                    ctx.file,
6385                    &enclosing_owner,
6386                    name,
6387                ),
6388                EnclosingMemberOwnerResolution::Missing
6389            )
6390        {
6391            return;
6392        }
6393        match resolve_bare_call_target(
6394            node,
6395            function,
6396            &ctx.analyzer,
6397            ctx.visibility,
6398            &ctx.ordinary_type_imports,
6399            ctx.file,
6400            ctx.source,
6401        ) {
6402            BareCallTargetResolution::FreeFunctions(units)
6403                if units
6404                    .iter()
6405                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6406            {
6407                if free_function_call_may_target(node, text, ctx) {
6408                    let recursive = enclosing_context(terminal, ctx)
6409                        .enclosing
6410                        .as_ref()
6411                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6412                    if recursive {
6413                        push_recursive_reference_hit(terminal, ctx);
6414                    } else {
6415                        push_hit(terminal, ctx);
6416                    }
6417                }
6418            }
6419            BareCallTargetResolution::UnprovenFreeFunctions(units)
6420                if units
6421                    .iter()
6422                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6423            {
6424                push_unproven_hit(terminal, ctx);
6425            }
6426            BareCallTargetResolution::FreeFunctions(_)
6427            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6428            | BareCallTargetResolution::Type(_)
6429            | BareCallTargetResolution::CallableShadow => {}
6430            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6431                push_unproven_hit(terminal, ctx);
6432            }
6433        }
6434        return;
6435    }
6436    if !free_function_call_may_target(node, text, ctx) {
6437        return;
6438    }
6439    if ctx.visibility.contains_named_symbol(
6440        ctx.file,
6441        text,
6442        TargetKind::FreeFunction,
6443        &ctx.spec.target,
6444    ) {
6445        push_hit(function_terminal_node(function), ctx);
6446    } else if ctx.visibility.resolve_known_non_target(
6447        ctx.file,
6448        text,
6449        TargetKind::FreeFunction,
6450        &ctx.spec.target,
6451    ) {
6452        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6453        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6454    } else {
6455        push_unproven_hit(function_terminal_node(function), ctx);
6456    }
6457}
6458
6459fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6460    same_visible_symbol(unit, &ctx.spec.target)
6461        || ctx
6462            .visibility
6463            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6464            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6465                &ctx.analyzer,
6466                ctx.visibility,
6467                unit,
6468                &ctx.spec.target,
6469            )
6470}
6471
6472/// Recover a bare call whose adjacent object-like macro arguments made
6473/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6474///
6475/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6476/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6477/// real block-scope function declaration remains under a `declaration`, so the
6478/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6479/// out of declaration syntax. The malformed parameter list cannot establish
6480/// arity; publish a proven hit only when ordinary visibility leaves one
6481/// callable identity for the bare name.
6482fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6483    if ctx
6484        .ancestry
6485        .parent(node)
6486        .is_none_or(|parent| parent.kind() != "ERROR")
6487        || !has_ancestor_kind(node, "compound_statement")
6488    {
6489        return;
6490    }
6491    let mut recovered_functions = Vec::new();
6492    if let Some(function) = node
6493        .child_by_field_name("declarator")
6494        .filter(|function| function.kind() == "identifier")
6495    {
6496        recovered_functions.push(function);
6497    }
6498    if let Some(parameters) = node.child_by_field_name("parameters") {
6499        let mut cursor = parameters.walk();
6500        for parameter in parameters
6501            .named_children(&mut cursor)
6502            .filter(|child| child.kind() == "parameter_declaration")
6503        {
6504            if parameter
6505                .child_by_field_name("declarator")
6506                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6507                && let Some(function) = parameter
6508                    .child_by_field_name("type")
6509                    .filter(|function| function.kind() == "type_identifier")
6510            {
6511                recovered_functions.push(function);
6512            }
6513        }
6514    }
6515
6516    for function in recovered_functions {
6517        let name = node_text(function, ctx.source);
6518        if !name_matches_callable(name, &ctx.spec.member_name)
6519            || ctx.local_shadows.is_shadowed(name)
6520        {
6521            continue;
6522        }
6523        *ctx.raw_match_count += 1;
6524        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6525            push_hit(function, ctx);
6526            continue;
6527        }
6528        let mut candidates =
6529            ctx.visibility
6530                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6531        candidates.retain(|candidate| {
6532            ctx.visibility.declaration_visible_at(
6533                &ctx.analyzer,
6534                ctx.file,
6535                candidate,
6536                function.start_byte(),
6537            )
6538        });
6539        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6540        if candidates
6541            .iter()
6542            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6543        {
6544            push_unproven_hit(function, ctx);
6545        }
6546    }
6547}
6548
6549/// Whether the bare name at `call` binds to exactly one visible callable, and
6550/// that callable is the scan target.
6551///
6552/// This is the scan-side reading of the #1811 rule: with one name binding there
6553/// is nothing an unknown argument count could select instead, so the site is a
6554/// proven reference rather than an unproven one. Only bare identifiers qualify;
6555/// a member or qualified call reaches its target through a receiver this cannot
6556/// judge.
6557fn bare_name_binds_only_target(
6558    call: Node<'_>,
6559    function: Node<'_>,
6560    text: &str,
6561    ctx: &ScanCtx<'_>,
6562) -> bool {
6563    if !matches!(function.kind(), "identifier" | "template_function") {
6564        return false;
6565    }
6566    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6567}
6568
6569fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6570    let mut candidates = ctx
6571        .visibility
6572        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6573    candidates.retain(|candidate| {
6574        ctx.visibility
6575            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6576    });
6577    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6578    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6579}
6580
6581fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6582    if ctx.spec.param_types.is_none() {
6583        return true;
6584    }
6585    let mut candidates = ctx
6586        .visibility
6587        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6588    candidates.retain(|candidate| {
6589        ctx.visibility
6590            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6591    });
6592    let Some(arity) = ctx
6593        .visibility
6594        .call_arity_evidence(ctx.file, call, ctx.source)
6595        .exact()
6596    else {
6597        return true;
6598    };
6599    candidates.retain(|unit| {
6600        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6601    });
6602    if candidates.is_empty()
6603        || !candidates
6604            .iter()
6605            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6606    {
6607        return true;
6608    }
6609    let arg_types = call_argument_types(call, ctx);
6610    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6611        candidates,
6612        &arg_types,
6613        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6614        &|candidate| {
6615            ctx.visibility
6616                .callable_is_template_declaration(&ctx.analyzer, candidate)
6617        },
6618        &|name| ctx.visibility.resolve_type(ctx.file, name),
6619        &|left, right| same_visible_symbol(left, right),
6620    );
6621    filtered
6622        .iter()
6623        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6624}
6625
6626/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6627/// in order.
6628///
6629/// A `None` entry is an argument the index cannot type; one of them makes
6630/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6631fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6632    argument_children(arguments)
6633        .map(|arg| expression_arg_type(arg, ctx))
6634        .collect()
6635}
6636
6637fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6638    call_arguments_node(call)
6639        .map(|arguments| argument_list_types(arguments, ctx))
6640        .unwrap_or_default()
6641}
6642
6643fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6644    match node.kind() {
6645        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6646        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6647            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6648            literal
6649        }),
6650        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6651        // `T v(x);` is the vexing parse: the grammar reads it as a function
6652        // declaration whose one parameter is `x` spelled as a type. A parameter
6653        // declaration that is nothing but a type identifier is that reading, and
6654        // the identifier it holds is the constructor argument.
6655        "parameter_declaration" => {
6656            let declared = node.child_by_field_name("type")?;
6657            (node.child_by_field_name("declarator").is_none()
6658                && declared.kind() == "type_identifier")
6659                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6660                .flatten()
6661        }
6662        // `this->m_params` names the same data member as the bare `m_params`.
6663        "field_expression" => {
6664            let receiver = node
6665                .child_by_field_name("argument")
6666                .or_else(|| node.named_child(0))?;
6667            let field = node.child_by_field_name("field")?;
6668            (receiver.kind() == "this")
6669                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6670                .flatten()
6671        }
6672        "parenthesized_expression" => node
6673            .child_by_field_name("argument")
6674            .or_else(|| node.named_child(0))
6675            .and_then(|inner| expression_arg_type(inner, ctx)),
6676        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
6677        // forward (#2552).
6678        "call_expression" => {
6679            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6680            expression_arg_type(forwarded, ctx)
6681        }
6682        "pointer_expression" => {
6683            let delta = match node.child_by_field_name("operator")?.kind() {
6684                "&" => 1,
6685                "*" => -1,
6686                _ => return None,
6687            };
6688            let inner = node
6689                .child_by_field_name("argument")
6690                .or_else(|| node.named_child(0))?;
6691            let mut arg_type = expression_arg_type(inner, ctx)?;
6692            arg_type.indirection += delta;
6693            Some(arg_type)
6694        }
6695        _ => None,
6696    }
6697}
6698
6699/// The type of a bare value name used as a call argument: the local or
6700/// parameter binding when one is in scope, otherwise the enclosing class's data
6701/// member of that name.
6702///
6703/// Unqualified lookup reaches a local before a data member, so a local of the
6704/// same name ends the search rather than falling through to the field. This
6705/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
6706/// the two directions agreeing about which overload a member-field argument
6707/// selects (#2894).
6708fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6709    match ctx.bindings.resolve_symbol(name) {
6710        SymbolResolution::Precise(bindings) => {
6711            bindings.iter().find_map(CppScanBinding::as_arg_type)
6712        }
6713        SymbolResolution::Ambiguous => None,
6714        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
6715            .then(|| enclosing_member_field_arg_type(node, name, ctx))
6716            .flatten(),
6717    }
6718}
6719
6720/// The declared type of `name` read as a data member of the class that lexically
6721/// owns `node`.
6722///
6723/// `None` when there is no enclosing class, when the name is not one data member
6724/// of it, or when the member's declared type does not resolve. An unknown
6725/// argument type makes the overload filter keep every candidate, which is the
6726/// honest answer for a field the index cannot type.
6727fn enclosing_member_field_arg_type(
6728    node: Node<'_>,
6729    name: &str,
6730    ctx: &ScanCtx<'_>,
6731) -> Option<CppArgType> {
6732    let owner = enclosing_context(node, ctx).owner?;
6733    let fields = ctx
6734        .visibility
6735        .visible_members_for_owner_name(ctx.file, &owner, name)
6736        .into_iter()
6737        .filter(|unit| unit.is_field())
6738        .collect::<Vec<_>>();
6739    let [field] = fields.as_slice() else {
6740        return None;
6741    };
6742    let (type_name, unit, indirection) =
6743        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
6744    Some(CppArgType {
6745        name: type_name,
6746        unit,
6747        indirection,
6748        pointee_const: false,
6749    })
6750}
6751
6752/// Record a *non-call* reference to a free function used as a value: `&foo`,
6753/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
6754/// `foo()` is recorded by the call_expression arm, and the function's own
6755/// declaration/definition name is not a reference.
6756fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6757    let text = node_text(node, ctx.source);
6758    if !name_matches_callable(text, &ctx.spec.member_name) {
6759        return;
6760    }
6761    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
6762        *ctx.raw_match_count += 1;
6763        match kind {
6764            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
6765            RecoveredCallableDeclarationKind::Definition => {
6766                push_recovered_definition_hit(node, ctx)
6767            }
6768        }
6769        return;
6770    }
6771    if is_declaration_name(node) {
6772        maybe_record_free_function_declaration_reference(node, ctx);
6773        return;
6774    }
6775    if is_call_callee_node(node) {
6776        return;
6777    }
6778    *ctx.raw_match_count += 1;
6779    if ctx.visibility.contains_named_symbol(
6780        ctx.file,
6781        text,
6782        TargetKind::FreeFunction,
6783        &ctx.spec.target,
6784    ) {
6785        push_hit(node, ctx);
6786    } else if ctx.visibility.resolve_known_non_target(
6787        ctx.file,
6788        text,
6789        TargetKind::FreeFunction,
6790        &ctx.spec.target,
6791    ) {
6792        // A qualified reference proven to a different namespace is not a match.
6793    } else {
6794        push_unproven_hit(node, ctx);
6795    }
6796}
6797
6798#[derive(Clone, Copy)]
6799enum RecoveredCallableDeclarationKind {
6800    Declaration,
6801    Definition,
6802}
6803
6804/// Recover the role of a callable name whose declaration was split by an
6805/// ERROR node. An indexed declaration range proves identity when available;
6806/// for a top-level C callable that recovery failed to range-index, same-file
6807/// name identity is sufficient because C has no function overloading. The CST
6808/// shape distinguishes a prototype from a definition. C++ parsing of
6809/// an unknown C return type can put the name under an ERROR child of the
6810/// function declarator, while call-shaped recovery puts that declarator under
6811/// an ERROR whose parent is a compound statement; only the former is admitted.
6812fn recovered_callable_declaration_kind(
6813    node: Node<'_>,
6814    ctx: &ScanCtx<'_>,
6815) -> Option<RecoveredCallableDeclarationKind> {
6816    if !matches!(node.kind(), "identifier" | "field_identifier") {
6817        return None;
6818    }
6819    let target_identity_proven =
6820        ctx.target_declaration_ranges.iter().any(|range| {
6821            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6822        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
6823            && ctx.spec.target.is_function()
6824            && ctx.spec.target.source() == ctx.file);
6825    let mut current = ctx.ancestry.parent(node);
6826    while let Some(parent) = current {
6827        if parent.is_error() {
6828            // C files with an unknown return type can retain the real
6829            // callable name as `function_declarator > ERROR`, with the ERROR
6830            // as a named child rather than the declarator field itself.
6831            let error = parent;
6832            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
6833                function.kind() == "function_declarator"
6834                    && function
6835                        .child_by_field_name("parameters")
6836                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
6837            }) else {
6838                current = ctx.ancestry.parent(parent);
6839                continue;
6840            };
6841            let container = ctx.ancestry.parent(function)?;
6842            let kind = match container.kind() {
6843                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6844                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6845                _ => return None,
6846            };
6847            return target_identity_proven.then_some(kind);
6848        }
6849        if parent.kind() == "function_declarator" {
6850            let error = ctx
6851                .ancestry
6852                .parent(parent)
6853                .filter(|parent| parent.is_error())?;
6854            let declarator = parent.child_by_field_name("declarator")?;
6855            if declarator.start_byte() > node.start_byte()
6856                || node.end_byte() > declarator.end_byte()
6857            {
6858                return None;
6859            }
6860            let container = ctx.ancestry.parent(error)?;
6861            let kind = match container.kind() {
6862                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6863                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6864                _ => return None,
6865            };
6866            return target_identity_proven.then_some(kind);
6867        }
6868        if parent.is_error() {
6869            current = ctx.ancestry.parent(parent);
6870            continue;
6871        }
6872        if matches!(
6873            parent.kind(),
6874            "translation_unit" | "function_definition" | "compound_statement"
6875        ) {
6876            return None;
6877        }
6878        current = ctx.ancestry.parent(parent);
6879    }
6880    None
6881}
6882
6883fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6884    if !ctx.spec.callable_has_definition_body {
6885        return;
6886    }
6887    let text = node_text(node, ctx.source);
6888    if !name_matches_callable(text, &ctx.spec.member_name) {
6889        return;
6890    }
6891    let mut declaration = ctx.ancestry.parent(node);
6892    while let Some(candidate) = declaration {
6893        if candidate.kind() == "function_definition" {
6894            return;
6895        }
6896        if candidate.kind() == "declaration" {
6897            break;
6898        }
6899        declaration = ctx.ancestry.parent(candidate);
6900    }
6901    if declaration.is_none() {
6902        return;
6903    }
6904    let linked_declaration = ctx
6905        .visibility
6906        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
6907        .into_iter()
6908        .find(|candidate| {
6909            candidate.source() == ctx.file
6910                && candidate.is_function()
6911                && (!ctx.target_group.contains(candidate)
6912                    || candidate.source() == ctx.spec.target.source())
6913                && ctx.analyzer.ranges(candidate).iter().any(|range| {
6914                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6915                })
6916                && (candidate.source() == ctx.spec.target.source()
6917                    && candidate.fq_name() == ctx.spec.target.fq_name()
6918                    && candidate.signature() == ctx.spec.target.signature()
6919                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
6920                        &ctx.analyzer,
6921                        ctx.visibility,
6922                        candidate,
6923                        &ctx.spec.target,
6924                    ))
6925                && callable_arity_matches_target(candidate, node, ctx)
6926        });
6927    if linked_declaration.is_none() {
6928        return;
6929    }
6930    *ctx.raw_match_count += 1;
6931    push_declaration_reference_hit(node, ctx);
6932}
6933
6934fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6935    let Some(function) = function_definition_name_node(node) else {
6936        return;
6937    };
6938    let text = node_text(function, ctx.source);
6939    if !name_matches_callable(text, &ctx.spec.member_name) {
6940        return;
6941    }
6942    *ctx.raw_match_count += 1;
6943    if !function_definition_signature_matches_target(node, ctx) {
6944        return;
6945    }
6946    if definition_name_candidates(function, ctx)
6947        .iter()
6948        .any(|name| {
6949            ctx.visibility.contains_named_symbol(
6950                ctx.file,
6951                name,
6952                TargetKind::FreeFunction,
6953                &ctx.spec.target,
6954            )
6955        })
6956    {
6957        push_definition_hit(function, ctx);
6958    } else if definition_name_candidates(function, ctx)
6959        .iter()
6960        .any(|name| {
6961            ctx.visibility.resolve_known_non_target(
6962                ctx.file,
6963                name,
6964                TargetKind::FreeFunction,
6965                &ctx.spec.target,
6966            )
6967        })
6968    {
6969        // A definition in another explicit namespace is a proven non-match.
6970    } else {
6971        push_unproven_definition_hit(function, ctx);
6972    }
6973}
6974
6975fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6976    if node.kind() == "preproc_arg" {
6977        maybe_record_function_macro_replacement_method_hits(node, ctx);
6978        return;
6979    }
6980    if node.kind() == "using_declaration" {
6981        maybe_record_using_callable_hit(node, ctx);
6982        return;
6983    }
6984    if has_ancestor_kind(node, "using_declaration") {
6985        return;
6986    }
6987    if node.kind() == "function_definition" {
6988        maybe_record_method_definition_hit(node, ctx);
6989        return;
6990    }
6991    if is_declaration_name(node) {
6992        return;
6993    }
6994    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
6995        maybe_record_qualified_method_value_hit(node, member, ctx);
6996        return;
6997    }
6998    if let Some(value) = qualified_callable_value(node) {
6999        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7000        return;
7001    }
7002    if let Some(call) = recovered_relational_template_member_call(node) {
7003        maybe_record_recovered_relational_template_method_hit(call, ctx);
7004        return;
7005    }
7006    if node.kind() != "call_expression" {
7007        return;
7008    }
7009    if let Some((receiver, operator)) = explicit_operator_call(node) {
7010        let text = node_text(operator, ctx.source);
7011        if !name_matches_callable(text, &ctx.spec.member_name) {
7012            return;
7013        }
7014        *ctx.raw_match_count += 1;
7015        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7016            match ctx
7017                .visibility
7018                .call_arity_evidence(ctx.file, node, ctx.source)
7019                .accepts(expected)
7020            {
7021                Some(true) => {}
7022                Some(false) => return,
7023                None => {
7024                    push_unproven_hit(operator, ctx);
7025                    return;
7026                }
7027            }
7028        }
7029        match explicit_receiver_target_resolution(
7030            receiver,
7031            ctx.visibility
7032                .call_arity_evidence(ctx.file, node, ctx.source)
7033                .exact(),
7034            ctx,
7035        ) {
7036            MethodReceiverTargetResolution::Target
7037                if receiver_is_self_like(
7038                    receiver,
7039                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7040                ) =>
7041            {
7042                push_self_receiver_hit(operator, ctx);
7043            }
7044            MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
7045            MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
7046            MethodReceiverTargetResolution::NonTarget
7047            | MethodReceiverTargetResolution::Ambiguous => {}
7048        }
7049        return;
7050    }
7051    let Some(function) = node
7052        .child_by_field_name("function")
7053        .or_else(|| node.named_child(0))
7054    else {
7055        return;
7056    };
7057    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7058        return;
7059    }
7060    if function.kind() == "identifier"
7061        && ctx
7062            .local_shadows
7063            .is_shadowed(node_text(function, ctx.source))
7064    {
7065        return;
7066    }
7067    *ctx.raw_match_count += 1;
7068    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7069        match ctx
7070            .visibility
7071            .call_arity_evidence(ctx.file, node, ctx.source)
7072            .accepts(expected)
7073        {
7074            Some(true) => {}
7075            Some(false) => return,
7076            None => {
7077                push_unproven_hit(function_terminal_node(function), ctx);
7078                return;
7079            }
7080        }
7081    }
7082    if !method_call_may_target(node, ctx) {
7083        return;
7084    }
7085    if is_structurally_qualified(function) {
7086        match qualified_owner_resolution(function, ctx) {
7087            QualifiedOwnerResolution::Target => {
7088                push_hit(function_terminal_node(function), ctx);
7089            }
7090            QualifiedOwnerResolution::NonTarget => {}
7091            QualifiedOwnerResolution::Unresolved => {
7092                push_unproven_hit(function_terminal_node(function), ctx);
7093            }
7094        }
7095        return;
7096    }
7097    match call_function_target_resolution(function, ctx) {
7098        MethodReceiverTargetResolution::Target
7099            if call_function_has_direct_self_receiver(
7100                function,
7101                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7102            ) =>
7103        {
7104            push_self_receiver_hit(function_terminal_node(function), ctx);
7105        }
7106        MethodReceiverTargetResolution::Target => {
7107            push_hit(function_terminal_node(function), ctx);
7108        }
7109        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7110        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7111        // the target member declared on a base is a genuine external usage of that inherited
7112        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7113        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7114        // arm because `same_owner_context` also accepts this inherited case.
7115        MethodReceiverTargetResolution::Missing
7116            if inherited_target_owner_context(function, ctx) =>
7117        {
7118            push_hit(function_terminal_node(function), ctx);
7119        }
7120        MethodReceiverTargetResolution::Missing
7121            if (matches!(function.kind(), "identifier" | "template_function")
7122                || call_function_has_direct_self_receiver(
7123                    function,
7124                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7125                ))
7126                && (same_owner_context(function, ctx)
7127                    || out_of_line_target_owner_context(function, ctx)) =>
7128        {
7129            push_self_receiver_hit(function_terminal_node(function), ctx);
7130        }
7131        MethodReceiverTargetResolution::Missing
7132            if function.kind() == "identifier"
7133                && resolves_to_lexical_free_function(function, ctx) =>
7134        {
7135            // A visible namespace/free function is a proven negative once the
7136            // enclosing structured owner and its hierarchy contain no such member.
7137        }
7138        MethodReceiverTargetResolution::Missing
7139            if !receiver_has_known_non_target(function, ctx)
7140                && !known_non_target_owner_context(function, ctx) =>
7141        {
7142            push_unproven_hit(function_terminal_node(function), ctx);
7143        }
7144        MethodReceiverTargetResolution::Missing => {}
7145    }
7146}
7147
7148/// Report member calls written inside a function-like macro's replacement.
7149///
7150/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7151/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7152/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7153/// structure, and every hit is reported at the bytes the member spells inside
7154/// that token.
7155fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7156    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7157        parent.kind() == "preproc_function_def"
7158            && parent
7159                .child_by_field_name("value")
7160                .is_some_and(|value| same_node(value, node))
7161    }) else {
7162        return;
7163    };
7164    let Some(body) = ctx
7165        .visibility
7166        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7167    else {
7168        return;
7169    };
7170    let Some(statements) = body.statements() else {
7171        return;
7172    };
7173    // Collect the candidate calls before typing anything: a replacement that
7174    // never names the queried member must not pay for its declarations.
7175    let mut candidates = Vec::new();
7176    let mut stack = vec![statements];
7177    while let Some(current) = stack.pop() {
7178        push_named_children_reversed(current, &mut stack);
7179        if current.kind() != "call_expression" {
7180            continue;
7181        }
7182        let Some(function) = current
7183            .child_by_field_name("function")
7184            .filter(|function| function.kind() == "field_expression")
7185        else {
7186            continue;
7187        };
7188        let Some(member) = function.child_by_field_name("field") else {
7189            continue;
7190        };
7191        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7192            continue;
7193        }
7194        let Some(receiver) = function
7195            .child_by_field_name("argument")
7196            .or_else(|| function.child_by_field_name("object"))
7197            .or_else(|| function.named_child(0))
7198        else {
7199            continue;
7200        };
7201        candidates.push((current, member, receiver));
7202    }
7203    if candidates.is_empty() {
7204        return;
7205    }
7206    let Some(replacement_start) =
7207        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7208    else {
7209        return;
7210    };
7211    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7212    for (current, member, receiver) in candidates {
7213        if *ctx.limit_exceeded {
7214            return;
7215        }
7216        let range = body.file_range(member, replacement_start);
7217        debug_assert_eq!(
7218            ctx.source.get(range.clone()),
7219            Some(node_text(member, &body.source)),
7220            "macro replacement member range must spell the member name"
7221        );
7222        *ctx.raw_match_count += 1;
7223        // The replacement's own macro environment is the one at the definition,
7224        // not at the sentinel offsets the recovered call carries.
7225        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7226            ctx.file,
7227            current,
7228            &body.source,
7229            definition.start_byte(),
7230        );
7231        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7232            match arity_evidence.accepts(expected) {
7233                Some(true) => {}
7234                Some(false) => continue,
7235                None => {
7236                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7237                    continue;
7238                }
7239            }
7240        }
7241        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7242        {
7243            MacroReplacementReceiver::Parameter => {
7244                if target_member_is_visible_candidate(ctx) {
7245                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7246                }
7247                continue;
7248            }
7249            MacroReplacementReceiver::Unknown => continue,
7250            MacroReplacementReceiver::Units(units) => {
7251                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7252            }
7253        };
7254        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7255            MethodReceiverTargetResolution::Target => {
7256                push_reference_hit_range(node, range.start, range.end, ctx);
7257            }
7258            MethodReceiverTargetResolution::Missing => {
7259                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7260            }
7261            MethodReceiverTargetResolution::NonTarget
7262            | MethodReceiverTargetResolution::Ambiguous => {}
7263        }
7264    }
7265}
7266
7267enum MacroReplacementReceiver {
7268    /// The receiver is one of the macro's parameters. Its type is supplied by
7269    /// each invocation, so a member call on it can never be proven here.
7270    Parameter,
7271    /// The receiver's declared type resolved to these units.
7272    Units(Vec<CodeUnit>),
7273    /// The receiver has a shape this path does not type.
7274    Unknown,
7275}
7276
7277fn macro_replacement_receiver(
7278    receiver: Node<'_>,
7279    body: &ParsedReplacementBody,
7280    locals: &HashMap<String, Option<CodeUnit>>,
7281    anchor: Node<'_>,
7282    ctx: &ScanCtx<'_>,
7283) -> MacroReplacementReceiver {
7284    let mut current = receiver;
7285    while matches!(
7286        current.kind(),
7287        "parenthesized_expression" | "pointer_expression"
7288    ) {
7289        let Some(inner) = current
7290            .child_by_field_name("argument")
7291            .or_else(|| current.named_child(0))
7292        else {
7293            return MacroReplacementReceiver::Unknown;
7294        };
7295        current = inner;
7296    }
7297    if !matches!(current.kind(), "identifier" | "field_identifier") {
7298        return MacroReplacementReceiver::Unknown;
7299    }
7300    let name = node_text(current, &body.source);
7301    if body.parameters.iter().any(|parameter| parameter == name) {
7302        return MacroReplacementReceiver::Parameter;
7303    }
7304    if let Some(local) = locals.get(name) {
7305        return match local {
7306            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7307            None => MacroReplacementReceiver::Unknown,
7308        };
7309    }
7310    // Neither a parameter nor a replacement-local: an ordinary name the macro
7311    // definition site sees, resolved exactly as the ordinary receiver path
7312    // resolves a bare identifier.
7313    let global_fields = ctx
7314        .visibility
7315        .visible_identifier_candidates(ctx.file, name)
7316        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7317        .collect::<Vec<_>>();
7318    if !global_fields.is_empty() {
7319        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7320            global_fields,
7321            anchor,
7322            ctx,
7323        ));
7324    }
7325    MacroReplacementReceiver::Units(
7326        ctx.visibility
7327            .resolve_type(ctx.file, name)
7328            .into_iter()
7329            .collect(),
7330    )
7331}
7332
7333/// Type the locals a macro replacement declares, keyed by declared name.
7334///
7335/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7336/// serves: the receiver's type is stated inside the replacement itself. A name
7337/// whose declared type does not resolve is kept with no unit, so a call on it
7338/// stays silent instead of falling through to file-scope name lookup.
7339fn macro_replacement_local_receivers(
7340    statements: Node<'_>,
7341    body: &ParsedReplacementBody,
7342    ctx: &ScanCtx<'_>,
7343) -> HashMap<String, Option<CodeUnit>> {
7344    let mut locals = HashMap::default();
7345    let mut stack = vec![statements];
7346    while let Some(current) = stack.pop() {
7347        push_named_children_reversed(current, &mut stack);
7348        if current.kind() != "declaration" {
7349            continue;
7350        }
7351        let Some(type_node) = current
7352            .child_by_field_name("type")
7353            .or_else(|| first_type_child(current))
7354        else {
7355            continue;
7356        };
7357        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7358        let mut cursor = current.walk();
7359        for child in current.named_children(&mut cursor) {
7360            let declarator = if child.kind() == "init_declarator" {
7361                child.child_by_field_name("declarator")
7362            } else {
7363                is_declarator_node(child).then_some(child)
7364            };
7365            let Some(name) =
7366                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7367            else {
7368                continue;
7369            };
7370            locals.insert(name, unit.clone());
7371        }
7372    }
7373    locals
7374}
7375
7376fn macro_replacement_declared_unit(
7377    type_node: Node<'_>,
7378    body: &ParsedReplacementBody,
7379    ctx: &ScanCtx<'_>,
7380) -> Option<CodeUnit> {
7381    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7382    let unit = match ctx
7383        .visibility
7384        .resolve_type_node_result(ctx.file, type_node, &body.source)
7385    {
7386        Ok(Some(unit)) => Some(unit),
7387        Ok(None) => ctx
7388            .visibility
7389            .canonical_type_for_reference(ctx.file, &name)
7390            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7391        Err(_) => None,
7392    }?;
7393    canonical_receiver_unit(&unit, ctx)
7394}
7395
7396/// Whether the queried member is a candidate this file can see.
7397///
7398/// A macro parameter receiver has no type at the definition, so a member call
7399/// on it is at most unproven. Requiring the target to be visible here keeps a
7400/// same-named member of an unrelated translation unit out of the result.
7401fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7402    ctx.visibility
7403        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7404        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7405}
7406
7407fn maybe_record_recovered_relational_template_method_hit(
7408    call: RecoveredRelationalTemplateMemberCall<'_>,
7409    ctx: &mut ScanCtx<'_>,
7410) {
7411    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7412        return;
7413    }
7414    *ctx.raw_match_count += 1;
7415    if !ctx
7416        .visibility
7417        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7418        || ctx
7419            .spec
7420            .callable_arity_at(call.member.start_byte())
7421            .is_some_and(|arity| !arity.accepts(call.arity))
7422    {
7423        return;
7424    }
7425    match explicit_receiver_target_resolution(call.receiver, Some(call.arity), ctx) {
7426        MethodReceiverTargetResolution::Target
7427            if receiver_is_self_like(
7428                call.receiver,
7429                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7430            ) =>
7431        {
7432            push_self_receiver_hit(call.member, ctx);
7433        }
7434        MethodReceiverTargetResolution::Target => push_hit(call.member, ctx),
7435        MethodReceiverTargetResolution::Missing => push_unproven_hit(call.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 ctx.visibility.visible_member_for_owner_name(
7532        ctx.file,
7533        target_owner,
7534        &ctx.spec.member_name,
7535    ) {
7536        VisibleMemberResolution::Callable(candidates)
7537            if candidates.iter().all(|candidate| {
7538                ctx.target_group.contains(candidate)
7539                    || ctx
7540                        .target_group
7541                        .iter()
7542                        .any(|target| same_visible_symbol(candidate, target))
7543            }) =>
7544        {
7545            push_hit(imported, ctx);
7546        }
7547        VisibleMemberResolution::NonCallable => {}
7548        VisibleMemberResolution::Callable(_)
7549        | VisibleMemberResolution::AmbiguousKind
7550        | VisibleMemberResolution::Missing => {
7551            push_unproven_hit(imported, ctx);
7552        }
7553    }
7554}
7555
7556fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7557    let name = node_text(node, ctx.source);
7558    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7559    let key = (namespace.clone(), name.to_string());
7560    if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7561        return resolved;
7562    }
7563    let resolved = ctx
7564        .visibility
7565        .visible_identifier_candidates(ctx.file, name)
7566        .any(|unit| {
7567            unit.is_function()
7568                && type_owner_of(&ctx.analyzer, unit).is_none()
7569                && unit.package_name() == namespace
7570        });
7571    ctx.lexical_free_function_cache
7572        .borrow_mut()
7573        .insert(key, resolved);
7574    resolved
7575}
7576
7577fn maybe_record_qualified_method_value_hit(
7578    qualified: Node<'_>,
7579    member: Node<'_>,
7580    ctx: &mut ScanCtx<'_>,
7581) {
7582    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7583        return;
7584    }
7585    *ctx.raw_match_count += 1;
7586    let resolution =
7587        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7588    match resolution {
7589        LexicalCallableValueResolution::Type(resolved_owner) => {
7590            let Some(owner) = ctx.spec.owner.as_ref() else {
7591                push_unproven_hit(member, ctx);
7592                return;
7593            };
7594            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7595                if same_visible_symbol(&resolved_owner, owner) {
7596                    push_unproven_hit(member, ctx);
7597                }
7598                return;
7599            }
7600            match ctx.visibility.visible_member_for_owner_name(
7601                ctx.file,
7602                owner,
7603                &ctx.spec.member_name,
7604            ) {
7605                VisibleMemberResolution::Callable(candidates)
7606                    if candidates.iter().all(|candidate| {
7607                        ctx.target_group.contains(candidate)
7608                            || ctx
7609                                .target_group
7610                                .iter()
7611                                .any(|target| same_visible_symbol(candidate, target))
7612                    }) =>
7613                {
7614                    // An explicitly qualified method value remains an external
7615                    // reference even when its owner is the enclosing class.
7616                    push_hit(member, ctx);
7617                }
7618                VisibleMemberResolution::NonCallable => {}
7619                VisibleMemberResolution::Callable(_)
7620                | VisibleMemberResolution::AmbiguousKind
7621                | VisibleMemberResolution::Missing => {
7622                    push_unproven_hit(member, ctx);
7623                }
7624            }
7625        }
7626        LexicalCallableValueResolution::FreeFunction(_) => {}
7627        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7628            push_unproven_hit(member, ctx);
7629        }
7630    }
7631}
7632
7633fn qualified_callable_value_resolution(
7634    qualified: Node<'_>,
7635    member_name: &str,
7636    ctx: &ScanCtx<'_>,
7637) -> LexicalCallableValueResolution {
7638    let Some((owner_components, global)) =
7639        qualified_callable_owner_components(qualified, ctx.source)
7640    else {
7641        return LexicalCallableValueResolution::Missing;
7642    };
7643    let lexical_scope = if global {
7644        Vec::new()
7645    } else {
7646        match enclosing_lexical_scope_components(
7647            qualified,
7648            &ctx.analyzer,
7649            ctx.visibility,
7650            ctx.file,
7651            ctx.source,
7652        ) {
7653            LexicalScopeResolution::Resolved(scope) => scope,
7654            LexicalScopeResolution::Ambiguous => {
7655                return LexicalCallableValueResolution::Ambiguous;
7656            }
7657            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7658        }
7659    };
7660    if let Some(target_owner) = ctx.spec.owner.as_ref()
7661        && let LexicalTypeResolution::Resolved { unit, .. } =
7662            resolve_type_components_lexically_at_for_target_with_scope_cache(
7663                qualified,
7664                &owner_components,
7665                global,
7666                &ctx.analyzer,
7667                ctx.visibility,
7668                &ctx.ordinary_type_imports,
7669                ctx.file,
7670                ctx.source,
7671                target_owner,
7672                false,
7673                Some(&ctx.lexical_scope_cache),
7674            )
7675    {
7676        return LexicalCallableValueResolution::Type(unit);
7677    }
7678    ctx.visibility.resolve_callable_value_components_lexically(
7679        &ctx.analyzer,
7680        ctx.file,
7681        &owner_components,
7682        member_name,
7683        global,
7684        &lexical_scope,
7685    )
7686}
7687
7688fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7689    let Some(owner) = ctx.spec.owner.as_ref() else {
7690        return true;
7691    };
7692    if ctx.spec.param_types.is_none() {
7693        return true;
7694    }
7695    let mut candidates = ctx
7696        .visibility
7697        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7698        .into_iter()
7699        .filter(|unit| unit.is_function())
7700        .cloned()
7701        .collect::<Vec<_>>();
7702    let Some(arity) = ctx
7703        .visibility
7704        .call_arity_evidence(ctx.file, call, ctx.source)
7705        .exact()
7706    else {
7707        return true;
7708    };
7709    candidates.retain(|unit| {
7710        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
7711    });
7712    if candidates.is_empty()
7713        || !candidates
7714            .iter()
7715            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7716    {
7717        return true;
7718    }
7719    let arg_types = call_argument_types(call, ctx);
7720    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
7721        candidates,
7722        &arg_types,
7723        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
7724        &|candidate| {
7725            ctx.visibility
7726                .callable_is_template_declaration(&ctx.analyzer, candidate)
7727        },
7728        &|name| ctx.visibility.resolve_type(ctx.file, name),
7729        &|left, right| same_visible_symbol(left, right),
7730    );
7731    filtered
7732        .iter()
7733        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7734}
7735
7736fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7737    let Some(function) = function_definition_name_node(node) else {
7738        return;
7739    };
7740    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7741        return;
7742    }
7743    *ctx.raw_match_count += 1;
7744    if !function_definition_signature_matches_target(node, ctx) {
7745        return;
7746    }
7747    if node_inside_target_declaration(function, ctx) {
7748        return;
7749    }
7750    if is_structurally_qualified(function) {
7751        match qualified_owner_resolution(function, ctx) {
7752            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
7753            QualifiedOwnerResolution::NonTarget => {}
7754            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7755        }
7756        return;
7757    }
7758    if definition_name_candidates(function, ctx)
7759        .iter()
7760        .any(|name| {
7761            name.contains("::")
7762                && ctx.visibility.contains_named_symbol(
7763                    ctx.file,
7764                    name,
7765                    TargetKind::Method,
7766                    &ctx.spec.target,
7767                )
7768        })
7769    {
7770        push_definition_hit(function, ctx);
7771    } else if definition_name_candidates(function, ctx)
7772        .iter()
7773        .any(|name| {
7774            ctx.visibility.resolve_known_non_target(
7775                ctx.file,
7776                name,
7777                TargetKind::Method,
7778                &ctx.spec.target,
7779            )
7780        })
7781        || known_non_target_owner_context(function, ctx)
7782    {
7783        // A method definition for another visible owner is a proven non-match.
7784    } else {
7785        push_unproven_definition_hit(function, ctx);
7786    }
7787}
7788
7789fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7790    ctx.target_declaration_ranges
7791        .iter()
7792        .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
7793}
7794
7795fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
7796    let mut receiver = None;
7797    let mut cursor = node.walk();
7798    for child in node.named_children(&mut cursor) {
7799        if child.kind() == "argument_list" {
7800            continue;
7801        }
7802        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
7803            return receiver.map(|receiver| (receiver, operator));
7804        }
7805        if receiver.is_none() {
7806            receiver = Some(child);
7807        }
7808    }
7809    None
7810}
7811
7812fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
7813    if node.kind() != "function_definition" {
7814        return None;
7815    }
7816    node.child_by_field_name("declarator")
7817        .and_then(declarator_name_node)
7818}
7819
7820fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
7821    function_definition_name_node(node)
7822}
7823
7824fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7825    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7826        return true;
7827    };
7828    let Some(candidate) = canonical_callable_at_node(
7829        node,
7830        &ctx.analyzer,
7831        ctx.visibility,
7832        ctx.file,
7833        ctx.source,
7834        ctx.spec.kind,
7835    ) else {
7836        return false;
7837    };
7838    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
7839        return false;
7840    };
7841    if !expected.accepts(actual_arity.total()) {
7842        return false;
7843    }
7844    ctx.spec.param_types.as_ref().is_none_or(|expected| {
7845        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
7846            .is_some_and(|actual| actual == *expected)
7847    })
7848}
7849
7850fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7851    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7852        return true;
7853    };
7854    cpp_callable_arity(&ctx.analyzer, candidate)
7855        .is_some_and(|actual| expected.accepts(actual.total()))
7856}
7857
7858fn canonical_callable_at_node(
7859    node: Node<'_>,
7860    analyzer: &CppGraphSource<'_>,
7861    visibility: &VisibilityIndex<'_>,
7862    file: &ProjectFile,
7863    source: &str,
7864    kind: TargetKind,
7865) -> Option<CodeUnit> {
7866    let name = callable_name_node(node)?;
7867    let name_range = (name.start_byte(), name.end_byte());
7868    let name_text = node_text(name, source);
7869    for candidate in visibility.named_candidates(file, name_text, kind) {
7870        if candidate.source() != file || !candidate.is_function() {
7871            continue;
7872        }
7873        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
7874            continue;
7875        };
7876        for occurrence in occurrences {
7877            if occurrence
7878                .name_range
7879                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
7880            {
7881                return Some(candidate);
7882            }
7883        }
7884    }
7885    None
7886}
7887
7888fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
7889    let name = if node.kind() == "function_definition" {
7890        function_definition_name_node(node)?
7891    } else {
7892        node.child_by_field_name("declarator")
7893            .and_then(declarator_name_node)?
7894    };
7895    Some(function_terminal_node(name))
7896}
7897
7898fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
7899    name_matches_callable(node_text(function_terminal_node(node), source), expected)
7900}
7901
7902fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
7903    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
7904    if raw.is_empty() {
7905        return Vec::new();
7906    }
7907    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
7908        return vec![raw];
7909    };
7910    if !raw.contains("::") {
7911        return vec![format!("{namespace}::{raw}")];
7912    }
7913    // fqname-M4: peeks at the raw first `::`-split token, including the empty
7914    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
7915    // shape as rust's `rust_reference_looks_external`); the shared structured
7916    // splitter filters empty segments, which would shift "which token is
7917    // first" for that one lead-`::` shape and is not proven equivalent here.
7918    if raw
7919        .split("::")
7920        .next()
7921        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
7922    {
7923        vec![format!("{namespace}::{raw}"), raw]
7924    } else {
7925        vec![raw]
7926    }
7927}
7928
7929fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7930    if node.kind() == kind {
7931        return Some(node);
7932    }
7933    let mut cursor = node.walk();
7934    for child in node.named_children(&mut cursor) {
7935        if let Some(found) = first_descendant_of_kind(child, kind) {
7936            return Some(found);
7937        }
7938    }
7939    None
7940}
7941
7942/// Whether a name leaf the field scans reached sits in a value position.
7943///
7944/// Every kind those scans admit already names a value except `type_identifier`.
7945/// tree-sitter spells a non-type template argument with type syntax, so the
7946/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
7947/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
7948/// Admit that leaf only where no visible type explains the spelling, which is
7949/// the order forward navigation applies before it reads the leaf in the value
7950/// namespace (#2556). Every other `type_identifier` is a type reference and
7951/// belongs to the type scan.
7952fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7953    node.kind() != "type_identifier"
7954        || (is_type_shaped_template_argument_name(node)
7955            && ctx
7956                .visibility
7957                .resolve_type(ctx.file, node_text(node, ctx.source))
7958                .is_none())
7959}
7960
7961fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7962    if is_c_offsetof_member_node(node) {
7963        return;
7964    }
7965    if matches!(node.kind(), "identifier" | "field_identifier")
7966        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
7967            .is_some()
7968    {
7969        return;
7970    }
7971    if !matches!(
7972        node.kind(),
7973        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
7974    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7975        || !scan_leaf_is_value_position(node, ctx)
7976        || is_declaration_name(node)
7977        || is_member_field_own_declarator(node, ctx)
7978        || is_selected_field_expression_member_descendant(node)
7979        || is_nested_in_qualified_identifier(node)
7980    {
7981        return;
7982    }
7983    *ctx.raw_match_count += 1;
7984    if global_field_resolves_to_target(node, ctx) {
7985        push_hit(node, ctx);
7986    } else if global_field_is_known_non_target(node, ctx) {
7987    } else {
7988        push_unproven_hit(node, ctx);
7989    }
7990}
7991
7992/// Whether `node` belongs to the selected-member side of any enclosing field
7993/// expression. A reference may be nested arbitrarily inside the receiver side
7994/// (for example, an argument to a call-built fluent receiver), so direct child
7995/// equality is insufficient: classify each ancestor by structured subtree
7996/// containment instead.
7997fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
7998    let candidate = node;
7999    while let Some(parent) = node.parent() {
8000        if parent.kind() == "field_expression" {
8001            if let Some(field) = parent.child_by_field_name("field")
8002                && node_is_within(field, candidate)
8003            {
8004                // The `template` disambiguator in `receiver.template f<Arg>()`
8005                // wraps the selected member in a `dependent_name` whose single
8006                // named child is the `template_*` node the plain spelling puts
8007                // directly under `field`. Unwrap it so both spellings reach the
8008                // same name/arguments split (#2196).
8009                let selected = match field.kind() {
8010                    "dependent_name" => field.named_child(0).unwrap_or(field),
8011                    _ => field,
8012                };
8013                let selected_name = match selected.kind() {
8014                    "template_method" | "template_function" | "template_type" => {
8015                        selected.child_by_field_name("name").unwrap_or(selected)
8016                    }
8017                    _ => selected,
8018                };
8019                if node_is_within(selected_name, candidate) {
8020                    return true;
8021                }
8022                // A template argument is structurally inside the field subtree,
8023                // but it is an independent reference rather than the selected
8024                // member name.
8025                node = parent;
8026                continue;
8027            }
8028            let receiver = parent
8029                .child_by_field_name("argument")
8030                .or_else(|| parent.child_by_field_name("object"))
8031                .or_else(|| parent.named_child(0));
8032            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8033                // Unknown grammar shape inside a field expression: fail closed
8034                // rather than treating it as a receiver reference.
8035                return true;
8036            }
8037        }
8038        node = parent;
8039    }
8040    false
8041}
8042
8043fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
8044    parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
8045}
8046
8047fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8048    let text = node_text(node, ctx.source);
8049    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8050        return false;
8051    }
8052    if text.contains("::") {
8053        return ctx.visibility.contains_named_symbol(
8054            ctx.file,
8055            text,
8056            TargetKind::GlobalField,
8057            &ctx.spec.target,
8058        );
8059    }
8060    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8061        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8062    {
8063        return ctx.visibility.contains_named_symbol(
8064            ctx.file,
8065            text,
8066            TargetKind::GlobalField,
8067            &ctx.spec.target,
8068        );
8069    }
8070    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8071        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8072            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8073                brokk_bifrost_core::analyzer::Language::Cpp,
8074                &namespace,
8075            ) == indexed_scope
8076        })
8077    {
8078        return ctx.visibility.contains_named_symbol(
8079            ctx.file,
8080            text,
8081            TargetKind::GlobalField,
8082            &ctx.spec.target,
8083        );
8084    }
8085    bare_global_field_uniquely_resolves_to_target(text, ctx)
8086}
8087
8088fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8089    let mut matched_target = false;
8090    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8091        if !has_persisted_global_field_identity(unit)
8092            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8093        {
8094            continue;
8095        }
8096        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8097            continue;
8098        }
8099        if same_visible_global_field_symbol(
8100            &ctx.analyzer,
8101            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8102            unit,
8103            &ctx.spec.target,
8104        ) {
8105            matched_target = true;
8106        } else {
8107            return false;
8108        }
8109    }
8110    matched_target
8111}
8112
8113pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8114    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8115    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8116    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8117    // analyzer for every same-named candidate's parent.
8118    unit.is_field() && !unit.short_name().contains('.')
8119}
8120
8121fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8122    let text = node_text(node, ctx.source);
8123    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8124        return true;
8125    }
8126    if text.contains("::") {
8127        return ctx.visibility.resolve_known_non_target(
8128            ctx.file,
8129            text,
8130            TargetKind::GlobalField,
8131            &ctx.spec.target,
8132        );
8133    }
8134    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8135        return false;
8136    };
8137    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8138        && ctx
8139            .visibility
8140            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8141            .any(|unit| {
8142                has_persisted_global_field_identity(unit)
8143                    && unit.identifier() == ctx.spec.member_name
8144                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8145                    && !same_visible_global_field_symbol(
8146                        &ctx.analyzer,
8147                        &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8148                        unit,
8149                        &ctx.spec.target,
8150                    )
8151            })
8152}
8153
8154fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8155    if is_c_offsetof_member_node(node) {
8156        maybe_record_c_offsetof_field_hit(node, ctx);
8157        return;
8158    }
8159    if node.kind() == "field_expression" {
8160        let Some(field) = node.child_by_field_name("field") else {
8161            return;
8162        };
8163        if node_text(field, ctx.source) != ctx.spec.member_name {
8164            return;
8165        }
8166        *ctx.raw_match_count += 1;
8167        let receiver = node
8168            .child_by_field_name("argument")
8169            .or_else(|| node.child_by_field_name("object"));
8170        let receiver_resolution =
8171            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8172        match receiver_resolution {
8173            Some(MethodReceiverTargetResolution::Target)
8174                if !ctx.analyzer.reference_uses_c_semantics(ctx.file)
8175                    || ctx.visibility.declaration_visible_at_reference(
8176                        &ctx.analyzer,
8177                        ctx.file,
8178                        &ctx.spec.target,
8179                        field,
8180                    ) =>
8181            {
8182                push_hit(field, ctx)
8183            }
8184            Some(MethodReceiverTargetResolution::Target) => {}
8185            Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
8186            Some(
8187                MethodReceiverTargetResolution::NonTarget
8188                | MethodReceiverTargetResolution::Ambiguous,
8189            ) => {}
8190        }
8191        return;
8192    }
8193
8194    if matches!(node.kind(), "identifier" | "field_identifier")
8195        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8196        && let Some(designator_owner) =
8197            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8198    {
8199        *ctx.raw_match_count += 1;
8200        match designator_owner {
8201            DesignatedInitializerOwner::Resolved(owner)
8202                if ctx
8203                    .spec
8204                    .owner
8205                    .as_ref()
8206                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8207            {
8208                push_hit(node, ctx);
8209            }
8210            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8211            DesignatedInitializerOwner::Resolved(_) => {}
8212        }
8213        return;
8214    }
8215
8216    let qualified_member_name_matches =
8217        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8218            && cpp_name_component_nodes(node)
8219                .and_then(|components| components.last().copied())
8220                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8221    if !matches!(
8222        node.kind(),
8223        "identifier"
8224            | "field_identifier"
8225            | "qualified_identifier"
8226            | "scoped_identifier"
8227            | "type_identifier"
8228    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8229        && !qualified_member_name_matches)
8230        || !scan_leaf_is_value_position(node, ctx)
8231        || is_declaration_name(node)
8232        || is_member_field_own_declarator(node, ctx)
8233        || is_selected_field_expression_member_descendant(node)
8234        || is_nested_in_qualified_identifier(node)
8235    {
8236        return;
8237    }
8238    *ctx.raw_match_count += 1;
8239    if is_structurally_qualified(node) {
8240        match qualified_owner_resolution(node, ctx) {
8241            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8242            QualifiedOwnerResolution::NonTarget => {}
8243            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8244        }
8245        return;
8246    }
8247    let text = node_text(node, ctx.source);
8248    if ctx.local_shadows.is_shadowed(text) {
8249        return;
8250    }
8251    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8252        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8253    let owner_context = structured_owner_context_resolution(node, ctx);
8254    if matches!(
8255        owner_context,
8256        StructuredOwnerContextResolution::SelfTarget
8257            | StructuredOwnerContextResolution::InheritedTarget
8258    ) || unscoped_enum_match
8259    {
8260        push_hit(node, ctx);
8261    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8262        .then_some(ctx.spec.owner.as_ref())
8263        .flatten()
8264    {
8265        let resolution =
8266            match resolve_active_using_enum_member(node, ctx) {
8267                ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8268                ActiveUsingEnumMemberResolution::Class(resolution) => {
8269                    if direct_class_member_shadows(node, ctx) {
8270                        return;
8271                    }
8272                    resolution
8273                }
8274                ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8275                    if let Some(owner) = structured_enclosing_owner(node, ctx) {
8276                        if direct_class_member_shadows(node, ctx) {
8277                            return;
8278                        }
8279                        let complete_same_file_leaf =
8280                            owner.source() == ctx.file
8281                                && ctx.analyzer.type_hierarchy_provider().is_some_and(
8282                                    |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
8283                                );
8284                        if !complete_same_file_leaf {
8285                            push_unproven_hit(node, ctx);
8286                            return;
8287                        }
8288                    }
8289                    match owner_context {
8290                        StructuredOwnerContextResolution::SelfTarget
8291                        | StructuredOwnerContextResolution::InheritedTarget
8292                        | StructuredOwnerContextResolution::NonTarget => return,
8293                        StructuredOwnerContextResolution::Ambiguous => {
8294                            push_unproven_hit(node, ctx);
8295                            return;
8296                        }
8297                        StructuredOwnerContextResolution::Missing => {}
8298                    }
8299                    if namespace_value_shadows(node, ctx) {
8300                        return;
8301                    }
8302                    resolution
8303                }
8304                ActiveUsingEnumMemberResolution::Missing => {
8305                    if direct_class_member_shadows(node, ctx)
8306                        || (structured_enclosing_owner(node, ctx).is_none()
8307                            && namespace_value_shadows(node, ctx))
8308                    {
8309                        return;
8310                    }
8311                    UsingEnumMemberResolution::Missing
8312                }
8313            };
8314        match resolution {
8315            UsingEnumMemberResolution::Resolved { owner, member }
8316                if same_visible_symbol(&owner, target_owner)
8317                    && same_visible_symbol(&member, &ctx.spec.target) =>
8318            {
8319                push_hit(node, ctx);
8320            }
8321            UsingEnumMemberResolution::Resolved { .. } => {}
8322            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8323                push_unproven_hit(node, ctx)
8324            }
8325        }
8326    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8327        push_unproven_hit(node, ctx);
8328    }
8329}
8330
8331fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8332    if node_text(node, ctx.source) != ctx.spec.member_name {
8333        return;
8334    }
8335    *ctx.raw_match_count += 1;
8336    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8337        push_unproven_hit(node, ctx);
8338        return;
8339    };
8340    let owner = match resolve_type_node_lexically(
8341        type_reference,
8342        &ctx.analyzer,
8343        ctx.visibility,
8344        &ctx.ordinary_type_imports,
8345        ctx.file,
8346        ctx.source,
8347    ) {
8348        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8349        LexicalTypeResolution::Resolved { .. }
8350        | LexicalTypeResolution::Ambiguous
8351        | LexicalTypeResolution::Missing => {
8352            push_unproven_hit(member, ctx);
8353            return;
8354        }
8355    };
8356    let candidates = ctx
8357        .visibility
8358        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8359        .into_iter()
8360        .filter(|candidate| candidate.is_field())
8361        .collect::<Vec<_>>();
8362    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8363        push_hit(member, ctx);
8364    } else if candidates.len() > 1 {
8365        push_unproven_hit(member, ctx);
8366    }
8367}
8368
8369enum ActiveUsingEnumMemberResolution {
8370    Block(UsingEnumMemberResolution),
8371    Class(UsingEnumMemberResolution),
8372    Namespace(UsingEnumMemberResolution),
8373    Missing,
8374}
8375
8376fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8377    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8378        ctx.visibility
8379            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8380            .into_iter()
8381            .next()
8382            .is_some()
8383    })
8384}
8385
8386fn resolve_active_using_enum_member(
8387    node: Node<'_>,
8388    ctx: &ScanCtx<'_>,
8389) -> ActiveUsingEnumMemberResolution {
8390    let block =
8391        ctx.using_enum_owners
8392            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8393    if !matches!(block, UsingEnumMemberResolution::Missing) {
8394        return ActiveUsingEnumMemberResolution::Block(block);
8395    }
8396    let class = structured_enclosing_owner(node, ctx);
8397    let namespace = enclosing_namespace_components(node, ctx.source);
8398    match ctx.semantic_using_enum_owners.resolve_member(
8399        ctx.visibility,
8400        ctx.file,
8401        class.as_ref(),
8402        &namespace,
8403        node.start_byte(),
8404        &ctx.spec.member_name,
8405    ) {
8406        SemanticUsingEnumMemberResolution::Class(resolution) => {
8407            ActiveUsingEnumMemberResolution::Class(resolution)
8408        }
8409        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8410            ActiveUsingEnumMemberResolution::Namespace(resolution)
8411        }
8412        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8413    }
8414}
8415
8416fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8417    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8418    !matches!(
8419        resolve_namespace_value(
8420            &ctx.analyzer,
8421            ctx.visibility,
8422            ctx.file,
8423            &namespace,
8424            &ctx.spec.member_name,
8425            node.start_byte(),
8426        ),
8427        NamespaceValueResolution::Missing
8428    )
8429}
8430
8431/// Whether `node` is a component of an enclosing qualified identifier's own
8432/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8433/// reference surfaced for it.
8434///
8435/// A qualified identifier owns only that path. Everything else beneath it is an
8436/// independent reference that its own target scanner must resolve:
8437///
8438/// - `Owner::Template<argument>` holds each template argument outside the path.
8439/// - Error recovery can hang a complete member initializer off an `ERROR` child
8440///   of a synthetic qualified identifier, where no structured path exists.
8441/// - The grammar accepts a `pointer_type_declarator` as a qualified
8442///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8443///   folds a whole parameter list under one `qualified_identifier`. Parameter
8444///   default values there are ordinary value expressions (#2548).
8445///
8446/// Follow the structured `scope`/`name` links rather than every ancestor, which
8447/// covers all three cases with one rule.
8448fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8449    if node.kind() == "qualified_identifier" {
8450        return false;
8451    }
8452    let mut current = node;
8453    while let Some(parent) = current.parent() {
8454        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8455            parent
8456                .child_by_field_name(field)
8457                .is_some_and(|component| same_node(component, current))
8458        });
8459        if !on_name_path {
8460            return false;
8461        }
8462        if parent.kind() == "qualified_identifier" {
8463            return true;
8464        }
8465        current = parent;
8466    }
8467    false
8468}
8469
8470fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8471    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8472}
8473
8474/// What a receiver spelled as a qualified name denotes.
8475///
8476/// `ns::value.run()` reads the member off the declared type of the variable
8477/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8478/// Forward navigation and the inverse walk both ask this before typing such a
8479/// receiver, so the two directions accept the same receiver spellings (#2547).
8480pub enum QualifiedReceiverBase {
8481    /// Every visible declaration of the one variable the path names.
8482    Variable(Vec<CodeUnit>),
8483    /// The type the path names when it names no variable.
8484    Type(Option<CodeUnit>),
8485    /// The path names more than one variable; typing either would be a guess.
8486    Ambiguous,
8487}
8488
8489pub fn qualified_receiver_base(
8490    visibility: &VisibilityIndex<'_>,
8491    file: &ProjectFile,
8492    receiver: Node<'_>,
8493    source: &str,
8494) -> QualifiedReceiverBase {
8495    let reference = node_text(receiver, source);
8496    let fields = visibility
8497        .named_candidates(file, reference, TargetKind::GlobalField)
8498        .into_iter()
8499        .filter(has_persisted_global_field_identity)
8500        .collect::<Vec<_>>();
8501    let Some(first) = fields.first() else {
8502        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8503    };
8504    if fields
8505        .iter()
8506        .skip(1)
8507        .any(|field| !same_visible_symbol(first, field))
8508    {
8509        return QualifiedReceiverBase::Ambiguous;
8510    }
8511    QualifiedReceiverBase::Variable(fields)
8512}
8513
8514fn receiver_type_units_with_budget(
8515    node: Node<'_>,
8516    source: &str,
8517    ctx: &ScanCtx<'_>,
8518    remaining_call_depth: usize,
8519) -> Vec<CodeUnit> {
8520    let mut current = node;
8521    let mut member_chain = Vec::new();
8522    let mut base_units = loop {
8523        match current.kind() {
8524            "field_expression" => {
8525                let Some(member) = current.child_by_field_name("field") else {
8526                    return Vec::new();
8527                };
8528                let Some(receiver) = current
8529                    .child_by_field_name("argument")
8530                    .or_else(|| current.child_by_field_name("object"))
8531                    .or_else(|| current.named_child(0))
8532                else {
8533                    return Vec::new();
8534                };
8535                member_chain.push(node_text(member, source));
8536                current = receiver;
8537            }
8538            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8539                let Some(inner) = current
8540                    .child_by_field_name("argument")
8541                    .or_else(|| current.named_child(0))
8542                else {
8543                    return Vec::new();
8544                };
8545                current = inner;
8546            }
8547            // Tree-sitter uses `field_identifier` for an unqualified member
8548            // field when it appears as the base of another field expression
8549            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8550            // same structured binding and enclosing-owner paths as an
8551            // ordinary identifier; falling through to `resolve_type` would
8552            // treat the field name as a type and lose the receiver identity.
8553            "identifier" | "field_identifier" => {
8554                let name = node_text(current, source);
8555                // Function-like macro locals are visible in the replacement
8556                // range after substitution and shadow caller bindings. The
8557                // receiver scan has no root field, so recover the source tree
8558                // root before asking the visibility index for that binding.
8559                let mut root = current;
8560                while let Some(parent) = ctx.ancestry.parent(root) {
8561                    root = parent;
8562                }
8563                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8564                    ctx.file,
8565                    root,
8566                    source,
8567                    current.start_byte(),
8568                    current.end_byte(),
8569                ) && binding.name == name
8570                {
8571                    let normalized = normalize_cpp_type_name(&binding.type_name);
8572                    let unit = binding
8573                        .proven_unit
8574                        .clone()
8575                        .or_else(|| {
8576                            binding.type_node.and_then(|type_node| {
8577                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8578                            })
8579                        })
8580                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8581                    // Keep the macro declaration's shadowing boundary even
8582                    // when its type is unavailable: an unresolved macro local
8583                    // cannot be reinterpreted as a static type or outer value.
8584                    break unit.into_iter().collect();
8585                }
8586                let local = ctx.bindings.resolve_symbol(name);
8587                if let Some(bindings) = local.as_precise() {
8588                    break receiver_units_from_bindings(current, bindings, ctx);
8589                }
8590                if ctx.bindings.is_shadowed(name) {
8591                    return Vec::new();
8592                }
8593                let owner = structured_enclosing_owner(current, ctx)
8594                    .filter(CodeUnit::is_class)
8595                    .or_else(|| {
8596                        enclosing_context(current, ctx)
8597                            .owner
8598                            .filter(CodeUnit::is_class)
8599                    });
8600                if let Some(owner) = owner {
8601                    // The enclosing class is only the search root: an implicit
8602                    // member-field receiver can be declared on any base, so the
8603                    // declaring owner comes from the same hierarchy walk the
8604                    // member chain below uses, not from an exact-parent match.
8605                    let declaring_owner = match resolve_declaring_member_owner(
8606                        &ctx.analyzer,
8607                        ctx.visibility,
8608                        ctx.file,
8609                        &owner,
8610                        name,
8611                    ) {
8612                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8613                        EnclosingMemberOwnerResolution::Missing => None,
8614                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8615                    };
8616                    if let Some(declaring_owner) = declaring_owner {
8617                        let implicit_fields = ctx
8618                            .visibility
8619                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8620                            .into_iter()
8621                            .filter(|unit| unit.is_field())
8622                            .collect::<Vec<_>>();
8623                        if !implicit_fields.is_empty() {
8624                            break receiver_units_from_declared_fields(
8625                                implicit_fields,
8626                                current,
8627                                ctx,
8628                            );
8629                        }
8630                    }
8631                }
8632                let global_fields = ctx
8633                    .visibility
8634                    .visible_identifier_candidates(ctx.file, name)
8635                    .filter(|unit| {
8636                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8637                    })
8638                    .collect::<Vec<_>>();
8639                if global_fields.is_empty() {
8640                    break ctx
8641                        .visibility
8642                        .resolve_type(ctx.file, name)
8643                        .into_iter()
8644                        .collect();
8645                }
8646                if let Some(first) = global_fields.first()
8647                    && global_fields.iter().skip(1).any(|field| {
8648                        !same_visible_global_field_symbol(
8649                            &ctx.analyzer,
8650                            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8651                            first,
8652                            field,
8653                        )
8654                    })
8655                {
8656                    return Vec::new();
8657                }
8658                break receiver_units_from_declared_fields(global_fields, current, ctx);
8659            }
8660            "call_expression" | "new_expression" => {
8661                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8662                    .and_then(|binding| binding.unit)
8663                    .into_iter()
8664                    .collect();
8665            }
8666            // `((T*)p)->member` is typed by what the cast spells, not by the
8667            // operand it converts (#2981). Pointer depth does not change which
8668            // type declares the member, so only the descriptor's type matters.
8669            "cast_expression" => {
8670                let Some(descriptor) = current.child_by_field_name("type") else {
8671                    return Vec::new();
8672                };
8673                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8674                    return Vec::new();
8675                };
8676                break unit.into_iter().collect();
8677            }
8678            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8679                let name = node_text(current, source);
8680                let local = ctx.bindings.resolve_symbol(name);
8681                if let Some(bindings) = local.as_precise() {
8682                    break receiver_units_from_bindings(current, bindings, ctx);
8683                }
8684                return Vec::new();
8685            }
8686            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8687            "qualified_identifier" | "scoped_identifier" => {
8688                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8689                    QualifiedReceiverBase::Variable(fields) => {
8690                        break receiver_units_from_declared_fields(
8691                            fields.iter().collect(),
8692                            current,
8693                            ctx,
8694                        );
8695                    }
8696                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
8697                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
8698                }
8699            }
8700            _ => {
8701                break ctx
8702                    .visibility
8703                    .resolve_type(ctx.file, node_text(current, source))
8704                    .into_iter()
8705                    .collect();
8706            }
8707        }
8708    };
8709
8710    base_units = canonical_receiver_units(base_units, ctx);
8711    if base_units.is_empty() {
8712        return Vec::new();
8713    }
8714
8715    while let Some(member_name) = member_chain.pop() {
8716        let mut next_units = Vec::new();
8717        for owner in &base_units {
8718            let declaring_owner = match resolve_declaring_member_owner(
8719                &ctx.analyzer,
8720                ctx.visibility,
8721                ctx.file,
8722                owner,
8723                member_name,
8724            ) {
8725                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8726                EnclosingMemberOwnerResolution::Missing => continue,
8727                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8728            };
8729            let fields = ctx.visibility.visible_members_for_owner_name(
8730                ctx.file,
8731                &declaring_owner,
8732                member_name,
8733            );
8734            for field in fields.into_iter().filter(|unit| unit.is_field()) {
8735                let Some(unit) =
8736                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8737                        .and_then(|binding| binding.unit)
8738                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
8739                else {
8740                    continue;
8741                };
8742                if !next_units
8743                    .iter()
8744                    .any(|existing| same_visible_symbol(existing, &unit))
8745                {
8746                    next_units.push(unit);
8747                }
8748            }
8749        }
8750        if next_units.is_empty() {
8751            return Vec::new();
8752        }
8753        base_units = unanimous_receiver_units(next_units);
8754        if base_units.is_empty() {
8755            return Vec::new();
8756        }
8757    }
8758    base_units
8759}
8760
8761fn receiver_units_from_bindings(
8762    node: Node<'_>,
8763    bindings: &HashSet<CppScanBinding>,
8764    ctx: &ScanCtx<'_>,
8765) -> Vec<CodeUnit> {
8766    let mut units = Vec::new();
8767    for binding in bindings {
8768        let raw_unit = if let Some(unit) = &binding.unit {
8769            unit.clone()
8770        } else {
8771            let Some(type_name) = binding.type_name.as_deref() else {
8772                return Vec::new();
8773            };
8774            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
8775                return Vec::new();
8776            };
8777            unit
8778        };
8779        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
8780            // Type aliases are represented as class units. When an alias has
8781            // a dependent target, canonicalization deliberately preserves
8782            // that alias rather than inventing a concrete class. Give the
8783            // existing target-guided structured recovery a chance to prove
8784            // the queried owner before accepting that unresolved identity.
8785            if same_visible_symbol(&unit, &raw_unit)
8786                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
8787            {
8788                units.push(recovered);
8789                continue;
8790            }
8791            units.push(unit);
8792            continue;
8793        }
8794        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
8795            units.push(unit);
8796            continue;
8797        }
8798        return Vec::new();
8799    }
8800    unanimous_receiver_units(units)
8801}
8802
8803/// Resolve a using-alias receiver from its declaration's structured RHS when
8804/// the alias target index cannot cross a malformed namespace-sentinel node.
8805/// The inverse target owner supplies only the exact class identity to prove;
8806/// lexical AST resolution still decides whether the alias denotes that class.
8807fn recovered_receiver_alias_target(
8808    reference: Node<'_>,
8809    alias: &CodeUnit,
8810    ctx: &ScanCtx<'_>,
8811) -> Option<CodeUnit> {
8812    if !ctx
8813        .analyzer
8814        .type_alias_provider()
8815        .is_some_and(|provider| provider.is_type_alias(alias))
8816    {
8817        return None;
8818    }
8819    let target = ctx.spec.owner.as_ref()?.clone();
8820    if !target.is_class() || alias.source() != ctx.file {
8821        return None;
8822    }
8823    let range = ctx
8824        .analyzer
8825        .ranges(alias)
8826        .into_iter()
8827        .find(|range| range.start_byte < range.end_byte)?;
8828    let mut node =
8829        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8830    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
8831        node = ctx.ancestry.parent(node)?;
8832    }
8833    let type_descriptor = node.child_by_field_name("type")?;
8834    let type_node = receiver_type_node_base(type_descriptor);
8835    let resolution = resolve_type_node_lexically_for_target(
8836        type_node,
8837        &ctx.analyzer,
8838        ctx.visibility,
8839        &ctx.ordinary_type_imports,
8840        ctx.file,
8841        ctx.source,
8842        &target,
8843        Some(&ctx.lexical_scope_cache),
8844        ctx.recovered_sentinel_scope(type_node).as_deref(),
8845    );
8846    if let LexicalTypeResolution::Resolved {
8847        unit, candidates, ..
8848    } = resolution
8849        && (same_visible_symbol(&unit, &target)
8850            || candidates
8851                .iter()
8852                .any(|candidate| same_visible_symbol(candidate, &target)))
8853    {
8854        return Some(target);
8855    }
8856    let (components, global) = type_reference_components(type_node, ctx.source)?;
8857    if !global
8858        && components.len() == 2
8859        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
8860    {
8861        let alias_provider = ctx.analyzer.type_alias_provider()?;
8862        let concrete = ctx
8863            .visibility
8864            .visible_identifier_candidates(ctx.file, &components[1])
8865            .filter(|candidate| {
8866                alias_provider.is_type_alias(candidate)
8867                    && !same_visible_symbol(candidate, alias)
8868                    && type_owner_of(&ctx.analyzer, candidate).is_some_and(|owner| owner.is_class())
8869                    && ctx.visibility.is_physically_visible(ctx.file, candidate)
8870                    && ctx
8871                        .visibility
8872                        .external_type_candidate_guard_compatible_in_context(
8873                            &ctx.analyzer,
8874                            ctx.file,
8875                            candidate,
8876                            type_node,
8877                        )
8878            })
8879            .filter_map(|candidate| {
8880                let canonical = ctx.visibility.canonical_visible_full_type_unit(
8881                    &ctx.analyzer,
8882                    ctx.file,
8883                    candidate,
8884                )?;
8885                // Another dependent alias can have the same nested name but
8886                // still canonicalize only to itself. It supplies no concrete
8887                // receiver identity and therefore cannot compete with an
8888                // alias that reaches an indexed class.
8889                (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
8890            })
8891            .collect::<Vec<_>>();
8892        if let [unit] = unanimous_receiver_units(concrete).as_slice()
8893            && same_visible_symbol(unit, &target)
8894        {
8895            return Some(target);
8896        }
8897    }
8898    let scope = ctx
8899        .recovered_sentinel_scope(type_node)
8900        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8901    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
8902    let visible = ctx.visibility.external_type_candidate_visible_in_context(
8903        &ctx.analyzer,
8904        ctx.file,
8905        &target,
8906        type_node,
8907    );
8908    (path_matches && visible).then_some(target)
8909}
8910
8911fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8912    let normalized = normalize_cpp_type_name(type_name);
8913    if normalized.is_empty() {
8914        return None;
8915    }
8916
8917    // A function-local alias is intentionally absent from the visibility
8918    // index. Recover its RHS from the structured alias declaration before
8919    // trying file-visible type lookup; this keeps the alias's lexical shadow
8920    // boundary intact.
8921    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
8922        match resolve_receiver_type_node(alias_type, ctx) {
8923            Ok(Some(unit)) => return Some(unit),
8924            Err(_) => return None,
8925            Ok(None) => {}
8926        }
8927    }
8928
8929    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
8930        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
8931        LexicalTypeResolution::Ambiguous => return None,
8932        LexicalTypeResolution::Missing => {}
8933    }
8934    let candidates = ctx
8935        .visibility
8936        .type_name_candidates(ctx.file, &normalized)
8937        .into_iter()
8938        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
8939        .collect();
8940    unanimous_receiver_units(candidates).into_iter().next()
8941}
8942
8943/// The receiver type a type node names: the visible indexed type it resolves
8944/// to, else the type its components name in the enclosing lexical scope.
8945///
8946/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
8947/// is unwrapped to its type specifier first, so every caller that has a spelled
8948/// type reads it the same way. `Err` reports a template whose arguments could
8949/// not be resolved; that is a structured failure, not an absent type.
8950fn resolve_receiver_type_node(
8951    type_node: Node<'_>,
8952    ctx: &ScanCtx<'_>,
8953) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
8954    let type_node = receiver_type_node_base(type_node);
8955    if let Some(unit) = ctx
8956        .visibility
8957        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
8958    {
8959        return Ok(Some(unit));
8960    }
8961    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
8962}
8963
8964fn resolve_receiver_type_node_lexically(
8965    type_node: Node<'_>,
8966    ctx: &ScanCtx<'_>,
8967) -> Option<CodeUnit> {
8968    let type_node = receiver_type_node_base(type_node);
8969    let components = cpp_type_name_components(type_node, ctx.source)?;
8970    let lexical_scope = match enclosing_lexical_scope_components(
8971        type_node,
8972        &ctx.analyzer,
8973        ctx.visibility,
8974        ctx.file,
8975        ctx.source,
8976    ) {
8977        LexicalScopeResolution::Resolved(scope) => scope,
8978        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
8979    };
8980    match ctx.visibility.resolve_type_components_lexically(
8981        &ctx.analyzer,
8982        ctx.file,
8983        &components,
8984        is_globally_qualified_cpp_name(type_node),
8985        &lexical_scope,
8986    ) {
8987        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
8988        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
8989    }
8990}
8991
8992fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
8993    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
8994        let Some(inner) = node.child_by_field_name("type").or_else(|| {
8995            if node.kind() == "dependent_type" {
8996                node.named_child(0)
8997            } else {
8998                None
8999            }
9000        }) else {
9001            break;
9002        };
9003        node = inner;
9004    }
9005    node
9006}
9007
9008fn resolve_receiver_type_name_lexically(
9009    node: Node<'_>,
9010    normalized: &str,
9011    ctx: &ScanCtx<'_>,
9012) -> LexicalTypeResolution {
9013    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9014        brokk_bifrost_core::analyzer::Language::Cpp,
9015        normalized,
9016    );
9017    if components.is_empty() {
9018        return LexicalTypeResolution::Missing;
9019    }
9020    let lexical_scope = match enclosing_lexical_scope_components(
9021        node,
9022        &ctx.analyzer,
9023        ctx.visibility,
9024        ctx.file,
9025        ctx.source,
9026    ) {
9027        LexicalScopeResolution::Resolved(scope) => scope,
9028        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9029        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9030    };
9031    ctx.visibility.resolve_type_components_lexically(
9032        &ctx.analyzer,
9033        ctx.file,
9034        &components,
9035        normalized.starts_with("::"),
9036        &lexical_scope,
9037    )
9038}
9039
9040fn local_receiver_alias_type_node<'tree>(
9041    node: Node<'tree>,
9042    name: &str,
9043    ctx: &ScanCtx<'_>,
9044) -> Option<Node<'tree>> {
9045    let callable = nearest_callable_scope(node)?;
9046    let mut root_callable = callable;
9047    // See `target_guided_nested_type_terminal_hit`: the result carries the
9048    // caller's tree lifetime.
9049    let mut ancestor = callable.parent();
9050    while let Some(current) = ancestor {
9051        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9052            root_callable = current;
9053        }
9054        ancestor = current.parent();
9055    }
9056
9057    let mut stack = vec![root_callable];
9058    let mut best = None;
9059    while let Some(current) = stack.pop() {
9060        if current.start_byte() >= node.start_byte() {
9061            continue;
9062        }
9063        if local_type_alias_name_node(current)
9064            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9065            && local_alias_scope_contains_node(current, node)
9066        {
9067            let replace = best
9068                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9069            if replace {
9070                best = current.child_by_field_name("type");
9071            }
9072        }
9073        let mut cursor = current.walk();
9074        stack.extend(current.named_children(&mut cursor));
9075    }
9076    best
9077}
9078
9079fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9080    let mut canonical = Vec::with_capacity(units.len());
9081    for unit in units {
9082        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9083            return Vec::new();
9084        };
9085        canonical.push(unit);
9086    }
9087    unanimous_receiver_units(canonical)
9088}
9089
9090fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9091    if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
9092        return cached.clone();
9093    }
9094    let canonical = ctx
9095        .visibility
9096        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9097    ctx.receiver_canonical_type_cache
9098        .borrow_mut()
9099        .insert(unit.clone(), canonical.clone());
9100    canonical
9101}
9102
9103fn receiver_units_from_declared_fields(
9104    fields: Vec<&CodeUnit>,
9105    reference: Node<'_>,
9106    ctx: &ScanCtx<'_>,
9107) -> Vec<CodeUnit> {
9108    let Some(first) = fields.first() else {
9109        return Vec::new();
9110    };
9111    if fields
9112        .iter()
9113        .skip(1)
9114        .any(|field| !same_visible_symbol(first, field))
9115    {
9116        return Vec::new();
9117    }
9118    unanimous_receiver_units(
9119        fields
9120            .into_iter()
9121            .filter_map(|field| {
9122                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9123                    .and_then(|binding| binding.unit)
9124                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9125            })
9126            .collect(),
9127    )
9128}
9129
9130/// Resolve a field receiver's declared type from its structured declaration
9131/// when the persisted type fact was built under a malformed sentinel scope.
9132/// The queried member owner supplies the exact class identity to prove; the
9133/// declaration's type node and recovered lexical path provide the evidence.
9134fn recovered_receiver_field_type(
9135    reference: Node<'_>,
9136    field: &CodeUnit,
9137    ctx: &ScanCtx<'_>,
9138) -> Option<CodeUnit> {
9139    let target = ctx.spec.owner.as_ref()?.clone();
9140    if !target.is_class() || field.source() != ctx.file {
9141        return None;
9142    }
9143    let range = ctx
9144        .analyzer
9145        .ranges(field)
9146        .into_iter()
9147        .find(|range| range.start_byte < range.end_byte)?;
9148    let mut declaration =
9149        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9150    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9151        declaration = ctx.ancestry.parent(declaration)?;
9152    }
9153    let type_node = first_type_child(declaration)?;
9154    let resolution = resolve_type_node_lexically_for_target(
9155        type_node,
9156        &ctx.analyzer,
9157        ctx.visibility,
9158        &ctx.ordinary_type_imports,
9159        ctx.file,
9160        ctx.source,
9161        &target,
9162        Some(&ctx.lexical_scope_cache),
9163        ctx.recovered_sentinel_scope(type_node).as_deref(),
9164    );
9165    if let LexicalTypeResolution::Resolved {
9166        unit, candidates, ..
9167    } = resolution
9168        && (same_visible_symbol(&unit, &target)
9169            || candidates
9170                .iter()
9171                .any(|candidate| same_visible_symbol(candidate, &target)))
9172    {
9173        return Some(target);
9174    }
9175    let type_node = receiver_type_node_base(type_node);
9176    let (components, global) = type_reference_components(type_node, ctx.source)?;
9177    let scope = ctx
9178        .recovered_sentinel_scope(type_node)
9179        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9180    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9181        && ctx.visibility.external_type_candidate_visible_in_context(
9182            &ctx.analyzer,
9183            ctx.file,
9184            &target,
9185            type_node,
9186        ))
9187    .then_some(target)
9188}
9189
9190fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9191    let mut unique = Vec::new();
9192    for unit in units {
9193        if !unique
9194            .iter()
9195            .any(|existing| same_visible_symbol(existing, &unit))
9196        {
9197            unique.push(unit);
9198            if unique.len() > 1 {
9199                return Vec::new();
9200            }
9201        }
9202    }
9203    unique
9204}
9205
9206fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9207    let Some(owner) = ctx.spec.owner.as_ref() else {
9208        return false;
9209    };
9210    match node.kind() {
9211        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9212        // When that structured lookup fails, the base expression's owner is not the field's
9213        // type: treating `value_` as `Value` would misclassify
9214        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9215        // with the `this` node itself, so failing closed for an unresolved field chain does not
9216        // discard genuine self calls.
9217        "field_expression" => false,
9218        "call_expression" => node
9219            .child_by_field_name("function")
9220            .is_some_and(|function| receiver_matches_target(function, ctx)),
9221        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9222            .child_by_field_name("argument")
9223            .or_else(|| node.named_child(0))
9224            .is_some_and(|child| receiver_matches_target(child, ctx)),
9225        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9226            .bindings
9227            .resolve_symbol(node_text(node, ctx.source))
9228            .as_precise()
9229            .is_some_and(|targets| {
9230                targets
9231                    .iter()
9232                    .filter_map(|target| target.unit.as_ref())
9233                    .any(|target| {
9234                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9235                    })
9236            }),
9237        "this" => same_owner_context(node, ctx),
9238        _ => qualified_owner_matches(node, ctx),
9239    }
9240}
9241
9242fn declaring_owner_for_explicit_receiver(
9243    receiver: Node<'_>,
9244    call_arity: Option<usize>,
9245    ctx: &ScanCtx<'_>,
9246) -> EnclosingMemberOwnerResolution {
9247    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9248        return EnclosingMemberOwnerResolution::Missing;
9249    }
9250    declaring_owner_from_receiver_units(
9251        receiver_type_units(receiver, ctx.source, ctx),
9252        receiver.start_byte(),
9253        call_arity,
9254        ctx,
9255    )
9256}
9257
9258/// The owner that declares the queried member for a receiver already typed.
9259///
9260/// `reference_byte` is the reference's position in the scanned file, which
9261/// decides which declarations are visible there. A receiver recovered from a
9262/// macro replacement is typed against the sentinel parse but still reported at
9263/// its bytes in the defining file, so the two arrive separately.
9264fn declaring_owner_from_receiver_units(
9265    receiver_units: Vec<CodeUnit>,
9266    reference_byte: usize,
9267    call_arity: Option<usize>,
9268    ctx: &ScanCtx<'_>,
9269) -> EnclosingMemberOwnerResolution {
9270    let mut declaring_owner = None;
9271    for receiver_owner in receiver_units {
9272        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9273            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9274        }) {
9275            if declaring_owner
9276                .as_ref()
9277                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9278            {
9279                return EnclosingMemberOwnerResolution::Ambiguous;
9280            }
9281            declaring_owner = Some(receiver_owner);
9282            continue;
9283        }
9284        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9285        let owner_resolution = match call_arity {
9286            Some(arity) => resolve_declaring_callable_owner(
9287                &ctx.analyzer,
9288                ctx.visibility,
9289                ctx.file,
9290                ordinary,
9291                &ctx.spec.member_name,
9292                arity,
9293            ),
9294            None => ordinary,
9295        };
9296        match owner_resolution {
9297            EnclosingMemberOwnerResolution::Owner(owner) => {
9298                if declaring_owner
9299                    .as_ref()
9300                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9301                {
9302                    return EnclosingMemberOwnerResolution::Ambiguous;
9303                }
9304                declaring_owner = Some(owner);
9305            }
9306            EnclosingMemberOwnerResolution::Ambiguous => {
9307                return EnclosingMemberOwnerResolution::Ambiguous;
9308            }
9309            EnclosingMemberOwnerResolution::Missing => {}
9310        }
9311    }
9312    declaring_owner
9313        .map(EnclosingMemberOwnerResolution::Owner)
9314        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9315}
9316
9317fn declaring_owner_from_call_function(
9318    function: Node<'_>,
9319    call_arity: Option<usize>,
9320    ctx: &ScanCtx<'_>,
9321) -> Option<EnclosingMemberOwnerResolution> {
9322    match function.kind() {
9323        "field_expression" => function
9324            .child_by_field_name("argument")
9325            .or_else(|| function.child_by_field_name("object"))
9326            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9327            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9328        "call_expression" => function
9329            .child_by_field_name("function")
9330            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9331        _ => None,
9332    }
9333}
9334
9335enum MethodReceiverTargetResolution {
9336    Target,
9337    NonTarget,
9338    Ambiguous,
9339    Missing,
9340}
9341
9342fn method_receiver_target_resolution(
9343    node: Node<'_>,
9344    declaring_owner: EnclosingMemberOwnerResolution,
9345    ctx: &ScanCtx<'_>,
9346) -> MethodReceiverTargetResolution {
9347    match declaring_owner {
9348        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9349            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9350        }
9351        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9352            MethodReceiverTargetResolution::Missing
9353        }
9354        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9355            MethodReceiverTargetResolution::Target
9356        }
9357        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9358            MethodReceiverTargetResolution::NonTarget
9359        }
9360        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9361    }
9362}
9363
9364/// Decide the queried member against a receiver's declaring owner alone.
9365///
9366/// This is the part of receiver typing that needs nothing but the owner and
9367/// the reference's position, so a member call recovered from a macro
9368/// replacement reaches the same verdict as ordinary code even though its
9369/// syntax lives in a separate sentinel parse.
9370fn declaring_owner_target_resolution(
9371    declaring_owner: EnclosingMemberOwnerResolution,
9372    reference_byte: usize,
9373    ctx: &ScanCtx<'_>,
9374) -> MethodReceiverTargetResolution {
9375    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9376        return MethodReceiverTargetResolution::Missing;
9377    };
9378    match declaring_owner {
9379        EnclosingMemberOwnerResolution::Owner(owner)
9380            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9381        {
9382            MethodReceiverTargetResolution::Target
9383        }
9384        EnclosingMemberOwnerResolution::Owner(owner)
9385            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9386        {
9387            MethodReceiverTargetResolution::NonTarget
9388        }
9389        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9390            MethodReceiverTargetResolution::Missing
9391        }
9392        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9393    }
9394}
9395
9396fn explicit_receiver_target_resolution(
9397    receiver: Node<'_>,
9398    call_arity: Option<usize>,
9399    ctx: &ScanCtx<'_>,
9400) -> MethodReceiverTargetResolution {
9401    method_receiver_target_resolution(
9402        receiver,
9403        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9404        ctx,
9405    )
9406}
9407
9408fn call_function_target_resolution(
9409    function: Node<'_>,
9410    ctx: &ScanCtx<'_>,
9411) -> MethodReceiverTargetResolution {
9412    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9413        (call.kind() == "call_expression")
9414            .then(|| {
9415                ctx.visibility
9416                    .call_arity_evidence(ctx.file, call, ctx.source)
9417                    .exact()
9418            })
9419            .flatten()
9420    });
9421    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9422    else {
9423        // A bare function identifier has an implicit receiver. Do not reinterpret
9424        // that identifier as a same-named type or value before enclosing-owner
9425        // lookup gets a chance to establish the member call.
9426        return MethodReceiverTargetResolution::Missing;
9427    };
9428    method_receiver_target_resolution(function, declaring_owner, ctx)
9429}
9430
9431fn receiver_owner_matches_target(
9432    receiver_owner: &CodeUnit,
9433    target_owner: &CodeUnit,
9434    reference_byte: usize,
9435    ctx: &ScanCtx<'_>,
9436) -> bool {
9437    same_symbol(receiver_owner, target_owner)
9438        || same_logical_symbol(receiver_owner, target_owner)
9439            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9440                || (ctx.spec.owner_is_forward_declaration
9441                    && ctx
9442                        .visibility
9443                        .is_physically_visible(ctx.file, receiver_owner))
9444                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9445                || target_group_contains_owner_peer(receiver_owner, ctx))
9446}
9447
9448fn receiver_owner_is_known_non_target(
9449    receiver_owner: &CodeUnit,
9450    target_owner: &CodeUnit,
9451    reference_byte: usize,
9452    ctx: &ScanCtx<'_>,
9453) -> bool {
9454    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9455        return false;
9456    }
9457    if !same_logical_symbol(receiver_owner, target_owner) {
9458        return true;
9459    }
9460    !ctx.target_group.iter().any(|target| {
9461        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9462    })
9463}
9464
9465fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9466    ctx.visibility
9467        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9468        && ctx.target_group.iter().any(|target| {
9469            type_owner_of(&ctx.analyzer, target)
9470                .as_ref()
9471                .is_some_and(|target_owner| {
9472                    same_symbol(target_owner, owner)
9473                        || (same_logical_symbol(target_owner, owner)
9474                            && target_owner.source() == owner.source())
9475                })
9476        })
9477}
9478
9479fn visible_target_peer_matches_owner(
9480    owner: &CodeUnit,
9481    reference_byte: usize,
9482    ctx: &ScanCtx<'_>,
9483) -> bool {
9484    ctx.visibility
9485        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9486        && ctx
9487            .visibility
9488            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9489            .any(|candidate| {
9490                cpp_callable_definitions_share_identity_evidence(
9491                    &ctx.analyzer,
9492                    candidate,
9493                    &ctx.spec.target,
9494                ) && ctx.visibility.declaration_visible_at(
9495                    &ctx.analyzer,
9496                    ctx.file,
9497                    candidate,
9498                    reference_byte,
9499                ) && type_owner_of(&ctx.analyzer, candidate)
9500                    .as_ref()
9501                    .is_some_and(|candidate_owner| {
9502                        same_symbol(candidate_owner, owner)
9503                            || (same_logical_symbol(candidate_owner, owner)
9504                                && candidate_owner.source() == owner.source())
9505                    })
9506            })
9507}
9508
9509/// Whether `node` is the implicit-object receiver of a member call.
9510///
9511/// `reference_is_c` is the compilation language the reference is read in
9512/// (#1970): `this` is the C++ implicit object only where C++ is what compiles
9513/// the source, and is an ordinary identifier in a `.c` file or a header every
9514/// reaching translation unit compiles as C.
9515fn receiver_is_self_like(node: Node<'_>, reference_is_c: bool) -> bool {
9516    match node.kind() {
9517        "this" => !reference_is_c,
9518        "pointer_expression" | "parenthesized_expression" => node
9519            .child_by_field_name("argument")
9520            .or_else(|| node.named_child(0))
9521            .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
9522        _ => false,
9523    }
9524}
9525
9526fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9527    match function.kind() {
9528        "field_expression" => function
9529            .child_by_field_name("argument")
9530            .or_else(|| function.child_by_field_name("object"))
9531            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9532        _ => receiver_is_self_like(function, reference_is_c),
9533    }
9534}
9535
9536fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9537    let Some(owner) = ctx.spec.owner.as_ref() else {
9538        return false;
9539    };
9540    match node.kind() {
9541        "field_expression" => node
9542            .child_by_field_name("argument")
9543            .or_else(|| node.child_by_field_name("object"))
9544            .is_some_and(|receiver| {
9545                // `this` names the enclosing class, not necessarily the class that
9546                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9547                // lookup decide whether this is a same-owner call, an inherited base
9548                // member, a nearer override, or an ambiguous base. Rejecting a derived
9549                // `this` merely because its immediate type differs from the target owner
9550                // drops genuine inherited calls (#2541).
9551                if receiver_is_self_like(
9552                    receiver,
9553                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9554                ) {
9555                    return false;
9556                }
9557                let units = receiver_type_units(receiver, ctx.source, ctx);
9558                !units.is_empty()
9559                    && units.iter().all(|target| {
9560                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9561                    })
9562            }),
9563        "call_expression" => node
9564            .child_by_field_name("function")
9565            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9566        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9567            .child_by_field_name("argument")
9568            .or_else(|| node.named_child(0))
9569            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9570        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9571            .bindings
9572            .resolve_symbol(node_text(node, ctx.source))
9573            .as_precise()
9574            .is_some_and(|targets| {
9575                let units = targets
9576                    .iter()
9577                    .filter_map(|target| target.unit.as_ref())
9578                    .collect::<Vec<_>>();
9579                !units.is_empty()
9580                    && units.iter().all(|target| {
9581                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9582                    })
9583            }),
9584        "this" => known_non_target_owner_context(node, ctx),
9585        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9586            qualified_owner_is_known_non_target(node, ctx)
9587        }
9588        _ => false,
9589    }
9590}
9591
9592#[derive(Clone, Copy, PartialEq, Eq)]
9593enum QualifiedOwnerResolution {
9594    Target,
9595    NonTarget,
9596    Unresolved,
9597}
9598
9599#[derive(Clone)]
9600pub enum LexicalScopeResolution {
9601    Resolved(Vec<String>),
9602    Ambiguous,
9603    Missing,
9604}
9605
9606/// One scan's memo of lexical scope reconstructions for one file, keyed by
9607/// the reconstruction's anchor range and flags, with the file's recovered
9608/// namespace regions that the key and the reconstruction both read.
9609pub struct LexicalScopeCache {
9610    resolutions: RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>,
9611    orphaned: Arc<OrphanedNamespaceScopeIndex>,
9612}
9613
9614impl LexicalScopeCache {
9615    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
9616        Self {
9617            resolutions: RefCell::new(HashMap::default()),
9618            orphaned: orphaned_namespace_scopes(visibility, file),
9619        }
9620    }
9621}
9622
9623/// The namespaces parse recovery dropped from `file`, memoized on the
9624/// analyzer (issue #1537).
9625fn orphaned_namespace_scopes(
9626    visibility: &VisibilityIndex<'_>,
9627    file: &ProjectFile,
9628) -> Arc<OrphanedNamespaceScopeIndex> {
9629    visibility
9630        .cpp()
9631        .orphaned_namespace_scopes(visibility.token(), file)
9632}
9633
9634fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9635    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
9636}
9637
9638fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9639    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
9640}
9641
9642fn is_structurally_qualified(node: Node<'_>) -> bool {
9643    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9644        && qualified_name_has_concrete_scope_separators(node)
9645}
9646
9647fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
9648    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9649        return QualifiedOwnerResolution::Unresolved;
9650    };
9651    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
9652        return QualifiedOwnerResolution::Unresolved;
9653    };
9654    // A malformed wrapper can make the parser-derived enclosing owner look
9655    // like the lexical namespace (for example tinyxml2's macro-prefixed
9656    // XMLHandle declarations). Recover the target namespace only while this
9657    // function owns scope reconstruction. An explicit recovered scope is
9658    // authoritative and enters the scoped resolver directly.
9659    if !global
9660        && !matches!(
9661            enclosing_lexical_scope_components(
9662                node,
9663                &ctx.analyzer,
9664                ctx.visibility,
9665                ctx.file,
9666                ctx.source,
9667            ),
9668            LexicalScopeResolution::Resolved(_)
9669        )
9670    {
9671        return QualifiedOwnerResolution::Unresolved;
9672    }
9673    match resolve_type_components_lexically_at_for_target_with_scope_cache(
9674        node,
9675        &components,
9676        global,
9677        &ctx.analyzer,
9678        ctx.visibility,
9679        &ctx.ordinary_type_imports,
9680        ctx.file,
9681        ctx.source,
9682        target_owner,
9683        false,
9684        Some(&ctx.lexical_scope_cache),
9685    ) {
9686        LexicalTypeResolution::Resolved { unit: owner, .. } => {
9687            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
9688                return QualifiedOwnerResolution::Target;
9689            }
9690            match cached_declaring_member_owner(&owner, ctx) {
9691                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9692                    if receiver_owner_matches_target(
9693                        &declaring_owner,
9694                        target_owner,
9695                        node.start_byte(),
9696                        ctx,
9697                    ) =>
9698                {
9699                    QualifiedOwnerResolution::Target
9700                }
9701                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9702                    if receiver_owner_is_known_non_target(
9703                        &declaring_owner,
9704                        target_owner,
9705                        node.start_byte(),
9706                        ctx,
9707                    ) =>
9708                {
9709                    QualifiedOwnerResolution::NonTarget
9710                }
9711                EnclosingMemberOwnerResolution::Owner(_)
9712                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
9713                EnclosingMemberOwnerResolution::Missing
9714                    if same_visible_symbol(&owner, target_owner) =>
9715                {
9716                    QualifiedOwnerResolution::Unresolved
9717                }
9718                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
9719            }
9720        }
9721        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
9722            QualifiedOwnerResolution::Unresolved
9723        }
9724    }
9725}
9726
9727fn qualified_callable_owner_components(
9728    node: Node<'_>,
9729    source: &str,
9730) -> Option<(Vec<String>, bool)> {
9731    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9732        || !qualified_name_has_concrete_scope_separators(node)
9733    {
9734        return None;
9735    }
9736    let global = is_globally_qualified_cpp_name(node);
9737    let mut components = Vec::new();
9738    append_cpp_name_components(node, source, &mut components)?;
9739    components.pop()?;
9740    (!components.is_empty()).then_some((components, global))
9741}
9742
9743fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
9744    if !matches!(
9745        node.kind(),
9746        "identifier"
9747            | "type_identifier"
9748            | "namespace_identifier"
9749            | "qualified_identifier"
9750            | "scoped_identifier"
9751            | "scoped_type_identifier"
9752            | "template_type"
9753            | "template_function"
9754    ) {
9755        return None;
9756    }
9757    let mut components = Vec::new();
9758    append_cpp_name_components(node, source, &mut components)?;
9759    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
9760}
9761
9762pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
9763    let mut namespaces = Vec::new();
9764    let mut current = node.parent();
9765    while let Some(parent) = current {
9766        if parent.kind() == "namespace_definition"
9767            && let Some(name) = parent.child_by_field_name("name")
9768        {
9769            let mut components = Vec::new();
9770            if append_cpp_name_components(name, source, &mut components).is_some() {
9771                namespaces.push(components);
9772            }
9773        }
9774        current = parent.parent();
9775    }
9776    namespaces.reverse();
9777    namespaces.into_iter().flatten().collect()
9778}
9779
9780pub fn enclosing_lexical_scope_components(
9781    node: Node<'_>,
9782    analyzer: &CppGraphSource<'_>,
9783    visibility: &VisibilityIndex<'_>,
9784    file: &ProjectFile,
9785    source: &str,
9786) -> LexicalScopeResolution {
9787    enclosing_lexical_scope_components_with_unresolved_owner(
9788        node,
9789        analyzer,
9790        visibility,
9791        file,
9792        source,
9793        false,
9794        false,
9795        &orphaned_namespace_scopes(visibility, file),
9796    )
9797}
9798
9799#[allow(clippy::too_many_arguments)]
9800fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
9801    node: Node<'_>,
9802    analyzer: &CppGraphSource<'_>,
9803    visibility: &VisibilityIndex<'_>,
9804    file: &ProjectFile,
9805    source: &str,
9806    allow_structured_unresolved_owner: bool,
9807    ignore_function_owner: bool,
9808    cache: Option<&LexicalScopeCache>,
9809) -> LexicalScopeResolution {
9810    let Some(cache) = cache else {
9811        return enclosing_lexical_scope_components_with_unresolved_owner(
9812            node,
9813            analyzer,
9814            visibility,
9815            file,
9816            source,
9817            allow_structured_unresolved_owner,
9818            ignore_function_owner,
9819            &orphaned_namespace_scopes(visibility, file),
9820        );
9821    };
9822    let orphaned = &cache.orphaned;
9823    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
9824    // A namespace body or translation unit anchor spans every recovered
9825    // region among its children, whose scopes differ; the region bounds keep
9826    // the key exact there and leave an anchor inside one region unchanged.
9827    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
9828        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
9829        None => (anchor_start, anchor_end),
9830    };
9831    let key = (
9832        anchor_start,
9833        anchor_end,
9834        allow_structured_unresolved_owner,
9835        ignore_function_owner,
9836    );
9837    if let Some(cached) = cache.resolutions.borrow().get(&key).cloned() {
9838        return cached;
9839    }
9840    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
9841        node,
9842        analyzer,
9843        visibility,
9844        file,
9845        source,
9846        allow_structured_unresolved_owner,
9847        ignore_function_owner,
9848        orphaned,
9849    );
9850    cache.resolutions.borrow_mut().insert(key, resolved.clone());
9851    resolved
9852}
9853
9854fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
9855    let mut current = node;
9856    loop {
9857        if matches!(
9858            current.kind(),
9859            "function_definition"
9860                | "class_specifier"
9861                | "struct_specifier"
9862                | "union_specifier"
9863                | "namespace_definition"
9864                | "translation_unit"
9865        ) {
9866            return (current.start_byte(), current.end_byte());
9867        }
9868        let Some(parent) = current.parent() else {
9869            return (current.start_byte(), current.end_byte());
9870        };
9871        current = parent;
9872    }
9873}
9874
9875#[allow(clippy::too_many_arguments)]
9876fn enclosing_lexical_scope_components_with_unresolved_owner(
9877    node: Node<'_>,
9878    analyzer: &CppGraphSource<'_>,
9879    visibility: &VisibilityIndex<'_>,
9880    file: &ProjectFile,
9881    source: &str,
9882    allow_structured_unresolved_owner: bool,
9883    ignore_function_owner: bool,
9884    orphaned: &OrphanedNamespaceScopeIndex,
9885) -> LexicalScopeResolution {
9886    #[cfg(any(test, feature = "test-support"))]
9887    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
9888    // One ancestor climb collects the namespace chain, the class chain, the
9889    // nearest function definition and both displaced-class-shape facts.
9890    // `Node::parent` re-descends from the root on every call (tree-sitter
9891    // 0.24+), so the four separate climbs this replaces each cost another
9892    // near-full-AST scan per reconstruction on a large flat file (#1927).
9893    let mut namespaces = Vec::new();
9894    let mut classes = Vec::new();
9895    let mut function_definition = None;
9896    let mut displaced_class_scope = false;
9897    let mut current = node.parent();
9898    while let Some(parent) = current {
9899        match parent.kind() {
9900            "namespace_definition" => {
9901                if let Some(name) = parent.child_by_field_name("name") {
9902                    let mut components = Vec::new();
9903                    if append_cpp_name_components(name, source, &mut components).is_some() {
9904                        namespaces.push((
9905                            parent.start_byte(),
9906                            components,
9907                            namespace_definition_has_real_braces(parent),
9908                        ));
9909                    }
9910                }
9911            }
9912            "class_specifier" | "struct_specifier" | "union_specifier" => {
9913                if let Some(name) = parent.child_by_field_name("name") {
9914                    let mut components = Vec::new();
9915                    if append_cpp_name_components(name, source, &mut components).is_some() {
9916                        classes.push(components);
9917                    }
9918                }
9919            }
9920            "function_definition" => {
9921                if function_definition.is_none() {
9922                    function_definition = Some(parent);
9923                }
9924                displaced_class_scope = displaced_class_scope
9925                    || parent.child_by_field_name("type").is_some_and(|type_node| {
9926                        matches!(
9927                            type_node.kind(),
9928                            "class_specifier" | "struct_specifier" | "union_specifier"
9929                        )
9930                    })
9931                    || is_malformed_wrapper_function_definition(parent);
9932            }
9933            _ => {}
9934        }
9935        current = parent.parent();
9936    }
9937    namespaces.reverse();
9938    // The parsed chain can stop short of the namespaces tree-sitter's error
9939    // recovery closed early; the orphaned index restores them (#1537).
9940    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
9941    let mut scope = namespace.clone();
9942    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
9943    // qualified function declarator. It is recovery scaffolding, not a real
9944    // callable owner, and must not overwrite the indexed class scope retained
9945    // by the wrapper body (#2249).
9946    let has_qualified_function_owner = function_definition
9947        .filter(|function| !is_malformed_wrapper_function_definition(*function))
9948        .and_then(function_definition_owner_lookup_node)
9949        .is_some_and(|owner| {
9950            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
9951        });
9952    let indexed_scope = displaced_class_scope
9953        .then(|| {
9954            indexed_structural_class_scope(visibility, file, node, source)
9955                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9956        })
9957        .flatten()
9958        .or_else(|| {
9959            // A qualified out-of-line definition can lose its class owner from
9960            // the parser tree when a namespace sentinel or export macro wraps
9961            // the declaration.  Recover the indexed owner scope up front so
9962            // all unqualified type references in the body see the same class
9963            // boundary as C++ lookup, including aliases in parameters and
9964            // local declarations (not only template-argument leaves).
9965            (has_qualified_function_owner && function_definition.is_some())
9966                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9967                .flatten()
9968                .filter(|indexed| {
9969                    qualified_owner_scope_is_recoverable(
9970                        indexed,
9971                        &namespace,
9972                        &classes,
9973                        function_definition
9974                            .and_then(function_definition_owner_lookup_node)
9975                            .and_then(|owner| qualified_callable_owner_components(owner, source))
9976                            .map(|(components, _)| components),
9977                    )
9978                })
9979        })
9980        .or_else(|| {
9981            // A nested class declaration can likewise lose one of its outer
9982            // class ancestors from the CST.  Prefer the exact indexed
9983            // structural class scope. Its same-file declaration range and
9984            // exact template-id match already prove the owner, including a
9985            // specialization whose parser component retains only the primary
9986            // name. Keep the suffix guard for graph-only recovery, which lacks
9987            // that direct syntax-range proof.
9988            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
9989                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
9990                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
9991                })
9992            })
9993        })
9994        .or_else(|| {
9995            // Retain the existing indexed lexical-scope recovery for
9996            // unqualified function bodies.  It is intentionally last so a
9997            // canonical class owner wins whenever one is available.
9998            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
9999                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10000                .flatten()
10001                .filter(|indexed| indexed.len() > namespace.len())
10002        });
10003    if let Some(indexed_scope) = indexed_scope.as_ref() {
10004        // A macro-displaced namespace can leave the parser with the real class
10005        // body but no namespace ancestor. Prefer the structural class match;
10006        // partial specializations whose structured name cannot round-trip use
10007        // the exact indexed enclosing-owner chain instead.
10008        scope = indexed_scope.clone();
10009        classes.clear();
10010    }
10011
10012    if !ignore_function_owner
10013        && has_qualified_function_owner
10014        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10015    {
10016        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10017            return LexicalScopeResolution::Missing;
10018        };
10019        // Resolve the out-of-line owner from the parser namespace before the
10020        // provisional indexed parent can influence the answer. Per-file
10021        // extraction can assign the first same-depth using namespace to a
10022        // bare owner. The structured using resolver instead selects the
10023        // namespace whose visible class has the owner name (#1838).
10024        let imports = visibility.ordinary_type_import_cell(file);
10025        let owner_resolution = resolve_type_components_lexically_at_scoped(
10026            function,
10027            &owner,
10028            global,
10029            analyzer,
10030            visibility,
10031            &imports,
10032            file,
10033            source,
10034            None,
10035            false,
10036            false,
10037            false,
10038            namespace.clone(),
10039        );
10040        match owner_resolution {
10041            LexicalTypeResolution::Resolved {
10042                unit, components, ..
10043            } if is_indexed_class_owner(analyzer, &unit) => {
10044                scope = components;
10045                classes.clear();
10046            }
10047            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10048            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10049                match visibility
10050                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10051                {
10052                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10053                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10054                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10055                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10056                            qualified_owner_scope_is_recoverable(
10057                                indexed,
10058                                &namespace,
10059                                &classes,
10060                                Some(owner.clone()),
10061                            )
10062                        }) {
10063                            scope = indexed.clone();
10064                        } else {
10065                            scope = if global || owner.starts_with(&namespace) {
10066                                owner
10067                            } else {
10068                                let mut relative = namespace;
10069                                relative.extend(owner);
10070                                relative
10071                            };
10072                        }
10073                    }
10074                    LexicalTypeResolution::Missing => {
10075                        // Structural lexical resolution cannot see an owner class that
10076                        // is reachable only through an in-scope `using namespace`
10077                        // directive, so it would otherwise hard-fail here. The indexed
10078                        // definition already carries the true fully-qualified owner
10079                        // (its package reflects the directive), so recover the real
10080                        // enclosing scope from the analyzer graph -- exactly the scope
10081                        // chain real C++ unqualified lookup traverses. Only the strict
10082                        // callers reach this arm; the best-effort callers above keep
10083                        // their existing structural guess (and its query profile).
10084                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10085                            .or_else(|| {
10086                                indexed_namespace_qualified_scope(
10087                                    analyzer, visibility, file, node, &owner,
10088                                )
10089                            }) {
10090                            Some(indexed) => scope = indexed,
10091                            None => return LexicalScopeResolution::Missing,
10092                        }
10093                    }
10094                }
10095            }
10096        }
10097    }
10098
10099    classes.reverse();
10100    scope.extend(classes.into_iter().flatten());
10101    LexicalScopeResolution::Resolved(scope)
10102}
10103
10104fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10105    let mut current = node.parent();
10106    while let Some(parent) = current {
10107        if parent.kind() == "function_definition"
10108            && parent.child_by_field_name("type").is_some_and(|type_node| {
10109                matches!(
10110                    type_node.kind(),
10111                    "class_specifier" | "struct_specifier" | "union_specifier"
10112                )
10113            })
10114        {
10115            return true;
10116        }
10117        current = parent.parent();
10118    }
10119    false
10120}
10121
10122fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10123    let mut current = node.parent();
10124    while let Some(parent) = current {
10125        if parent.kind() == "function_definition"
10126            && is_malformed_wrapper_function_definition(parent)
10127        {
10128            return true;
10129        }
10130        current = parent.parent();
10131    }
10132    false
10133}
10134
10135fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10136    node.has_error()
10137        && node
10138            .child_by_field_name("declarator")
10139            .is_some_and(|declarator| {
10140                declarator.kind() != "function_declarator"
10141                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10142            })
10143}
10144
10145/// Tree-sitter can make an attribute/nullability macro look like the namespace
10146/// component of a qualified function owner when it appears between the return
10147/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10148/// The recovered owner is not a C++ lexical owner, so callers resolving the
10149/// ordinary return/parameter type must retain the surrounding namespace scope.
10150fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10151    node.child_by_field_name("scope")
10152        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10153        .is_some()
10154}
10155
10156fn indexed_structural_class_scope(
10157    visibility: &VisibilityIndex<'_>,
10158    file: &ProjectFile,
10159    node: Node<'_>,
10160    source: &str,
10161) -> Option<Vec<String>> {
10162    let mut current = node.parent();
10163    while let Some(parent) = current {
10164        if matches!(
10165            parent.kind(),
10166            "class_specifier" | "struct_specifier" | "union_specifier"
10167        ) {
10168            return visibility.indexed_structural_class_scope(file, parent, source);
10169        }
10170        current = parent.parent();
10171    }
10172    None
10173}
10174
10175/// Check that an indexed owner scope is a structured completion of the parser
10176/// scope rather than an unrelated same-spelled declaration.
10177///
10178/// Error recovery around C++ namespace sentinels can preserve only a subset of
10179/// the namespace/class chain.  The indexed definition still carries the full
10180/// owner path, so require every surviving parser component to occur in order
10181/// and require any explicit qualified function owner to be the terminal
10182/// suffix.  An empty parser scope is accepted only with that qualified-owner
10183/// suffix evidence; a lone top-level short name is not evidence that a
10184/// namespace was lost.
10185fn qualified_owner_scope_is_recoverable(
10186    indexed: &[String],
10187    namespace: &[String],
10188    classes: &[Vec<String>],
10189    qualified_owner: Option<Vec<String>>,
10190) -> bool {
10191    if let Some(owner) = qualified_owner {
10192        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10193            return false;
10194        }
10195        // A malformed namespace sentinel can erase every parser namespace
10196        // ancestor.  The indexed enclosing callable still provides an
10197        // authoritative class owner, so the qualified owner suffix itself is
10198        // enough evidence in that case.  When namespace components survived,
10199        // retain the stricter subsequence check below.
10200        if namespace.is_empty() {
10201            return true;
10202        }
10203        if indexed.len() <= namespace.len() {
10204            return false;
10205        }
10206        let mut prefix = indexed.iter();
10207        return namespace
10208            .iter()
10209            .all(|component| prefix.any(|candidate| candidate == component));
10210    }
10211    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10212    if !class_components.is_empty() {
10213        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10214    }
10215    if namespace.is_empty() || indexed.len() <= namespace.len() {
10216        return false;
10217    }
10218    let mut prefix = indexed.iter();
10219    namespace
10220        .iter()
10221        .all(|component| prefix.any(|candidate| candidate == component))
10222}
10223
10224/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10225/// counts as the true lexical owner recovered from the indexed graph.
10226fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10227    unit.is_class()
10228        && !analyzer
10229            .type_alias_provider()
10230            .is_some_and(|provider| provider.is_type_alias(unit))
10231}
10232
10233/// Recover the enclosing member's true lexical scope from the *indexed*
10234/// definition when structural resolution cannot see the owner class.
10235///
10236/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10237/// {...}`) whose owner class is reachable only through an in-scope `using
10238/// namespace X;` directive cannot be resolved by `resolve_type_components_
10239/// lexically`, which walks structural lexical tiers and never consults
10240/// using-directives. The definition itself, however, is indexed with its true
10241/// fully-qualified identity (its package already reflects the directive), so
10242/// the analyzer graph knows the real owner. Walk from the reference's indexed
10243/// enclosing code unit up to the innermost enclosing class and return that
10244/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10245/// -- exactly the scope chain C++ unqualified lookup traverses.
10246fn indexed_enclosing_owner_scope(
10247    analyzer: &CppGraphSource<'_>,
10248    visibility: &VisibilityIndex<'_>,
10249    file: &ProjectFile,
10250    node: Node<'_>,
10251) -> Option<Vec<String>> {
10252    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10253}
10254
10255/// Recover the lexical scope of an out-of-line definition whose syntactic
10256/// qualifier names a *namespace* rather than a class (`void out::target(int)
10257/// {...}` written at file scope, issue #3096).
10258///
10259/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10260/// nothing here and the strict caller would fail closed for every unqualified
10261/// reference in the body. C++ unqualified lookup inside such a definition
10262/// proceeds from the named namespace outward, exactly as inside a `namespace
10263/// out { ... }` block, and the indexed definition already carries that scope
10264/// (`["out"]` for `out::target`).
10265///
10266/// Take it only when a namespace of that name is visible from the file and the
10267/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10268/// the graph does not hold (`void MissingContainer::call()`) has no such
10269/// namespace, so it still fails closed -- and it is rejected by the in-memory
10270/// visibility index, before any indexed enclosing-unit query.
10271fn indexed_namespace_qualified_scope(
10272    analyzer: &CppGraphSource<'_>,
10273    visibility: &VisibilityIndex<'_>,
10274    file: &ProjectFile,
10275    node: Node<'_>,
10276    owner: &[String],
10277) -> Option<Vec<String>> {
10278    let name = owner.last().expect("a qualified owner has one component");
10279    if !visibility
10280        .visible_identifier_candidates(file, name)
10281        .any(|unit| unit.is_module())
10282    {
10283        return None;
10284    }
10285    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10286    indexed.ends_with(owner).then_some(indexed)
10287}
10288
10289fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10290    let start = enclosing_context(node, ctx).enclosing?;
10291    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10292        ctx.analyzer.parent_of(unit)
10293    })
10294    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10295}
10296
10297pub fn resolve_type_node_lexically(
10298    node: Node<'_>,
10299    analyzer: &CppGraphSource<'_>,
10300    visibility: &VisibilityIndex<'_>,
10301    ordinary_type_imports: &OrdinaryTypeImportCell,
10302    file: &ProjectFile,
10303    source: &str,
10304) -> LexicalTypeResolution {
10305    let Some((components, global)) = type_reference_components(node, source) else {
10306        return LexicalTypeResolution::Missing;
10307    };
10308    let resolution = resolve_type_components_lexically_at(
10309        node,
10310        &components,
10311        global,
10312        analyzer,
10313        visibility,
10314        ordinary_type_imports,
10315        file,
10316        source,
10317    );
10318    if !is_cpp_template_argument_type_leaf(node) {
10319        return resolution;
10320    }
10321
10322    // Error recovery can detach a member function from its class while
10323    // leaving an unqualified type argument (for example `error_type` in
10324    // `expected<..., error_type>`). The normal structural scope then lacks
10325    // the class owner and resolves the wrong same-spelled alias, or fails
10326    // closed. The indexed enclosing unit still carries the authoritative
10327    // class scope; retry only this narrowly-shaped leaf with that scope.
10328    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10329        return resolution;
10330    };
10331    let namespace_scope = enclosing_namespace_components(node, source);
10332    if indexed_scope.len() <= namespace_scope.len() {
10333        return resolution;
10334    }
10335    let indexed = visibility.resolve_type_components_lexically(
10336        analyzer,
10337        file,
10338        &components,
10339        global,
10340        &indexed_scope,
10341    );
10342    match indexed {
10343        LexicalTypeResolution::Resolved { ref unit, .. }
10344            if !visibility
10345                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10346        {
10347            resolution
10348        }
10349        LexicalTypeResolution::Resolved { .. } => indexed,
10350        _ => resolution,
10351    }
10352}
10353
10354#[allow(clippy::too_many_arguments)]
10355pub fn resolve_type_node_lexically_for_target(
10356    node: Node<'_>,
10357    analyzer: &CppGraphSource<'_>,
10358    visibility: &VisibilityIndex<'_>,
10359    ordinary_type_imports: &OrdinaryTypeImportCell,
10360    file: &ProjectFile,
10361    source: &str,
10362    target: &CodeUnit,
10363    scope_cache: Option<&LexicalScopeCache>,
10364    recovered_scope: Option<&[String]>,
10365) -> LexicalTypeResolution {
10366    let Some((reference_components, global)) = type_reference_components(node, source) else {
10367        return LexicalTypeResolution::Missing;
10368    };
10369    let terminal = reference_components
10370        .last()
10371        .expect("type reference components are non-empty");
10372    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
10373        return LexicalTypeResolution::Missing;
10374    }
10375    let template_arguments = cpp_template_reference_arguments(node, source);
10376    let selects_concrete_specialization =
10377        template_arguments.is_some() && visibility.is_template_specialization(target);
10378    if !selects_concrete_specialization
10379        && !visibility.structured_type_reference_may_resolve_to_target(
10380            analyzer,
10381            file,
10382            std::slice::from_ref(terminal),
10383            false,
10384            &[],
10385            target,
10386        )
10387    {
10388        return LexicalTypeResolution::Missing;
10389    }
10390    if let Some(arguments) = template_arguments.as_ref() {
10391        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
10392            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10393                node,
10394                &reference_components,
10395                global,
10396                analyzer,
10397                visibility,
10398                ordinary_type_imports,
10399                file,
10400                source,
10401                recovered_scope,
10402            )
10403        } else {
10404            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10405                node,
10406                &reference_components,
10407                global,
10408                analyzer,
10409                visibility,
10410                ordinary_type_imports,
10411                file,
10412                source,
10413                scope_cache,
10414            )
10415        };
10416        return match alias_resolution {
10417            LexicalTypeResolution::Resolved {
10418                unit,
10419                components,
10420                candidates,
10421            } if visibility.template_alias_arguments_preserve_target(
10422                analyzer, file, &unit, arguments, target,
10423            ) =>
10424            {
10425                LexicalTypeResolution::Resolved {
10426                    unit: target.clone(),
10427                    components,
10428                    candidates,
10429                }
10430            }
10431            LexicalTypeResolution::Resolved {
10432                unit,
10433                components,
10434                candidates,
10435            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
10436                Ok(resolved_unit) => {
10437                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
10438                        .then(|| {
10439                            target_guided_malformed_template_alias_resolution(
10440                                node,
10441                                analyzer,
10442                                visibility,
10443                                file,
10444                                arguments,
10445                                &reference_components,
10446                                target,
10447                            )
10448                        })
10449                        .flatten();
10450                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
10451                        unit: resolved_unit,
10452                        components,
10453                        candidates,
10454                    })
10455                }
10456                Err(_) => LexicalTypeResolution::Ambiguous,
10457            },
10458            LexicalTypeResolution::Missing => {
10459                let target_preserving = if let Some(recovered_scope) = recovered_scope {
10460                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
10461                        node,
10462                        &reference_components,
10463                        global,
10464                        analyzer,
10465                        visibility,
10466                        ordinary_type_imports,
10467                        file,
10468                        source,
10469                        target,
10470                        true,
10471                        recovered_scope,
10472                    )
10473                } else {
10474                    resolve_type_components_lexically_at_for_target_with_scope_cache(
10475                        node,
10476                        &reference_components,
10477                        global,
10478                        analyzer,
10479                        visibility,
10480                        ordinary_type_imports,
10481                        file,
10482                        source,
10483                        target,
10484                        true,
10485                        scope_cache,
10486                    )
10487                };
10488                match target_preserving {
10489                    LexicalTypeResolution::Resolved {
10490                        unit: _,
10491                        components,
10492                        candidates,
10493                    } if template_reference_candidates_select_target(
10494                        node,
10495                        &candidates,
10496                        analyzer,
10497                        visibility,
10498                        file,
10499                        source,
10500                        target,
10501                    ) =>
10502                    {
10503                        LexicalTypeResolution::Resolved {
10504                            unit: target.clone(),
10505                            components,
10506                            candidates,
10507                        }
10508                    }
10509                    _ => target_guided_malformed_template_alias_resolution(
10510                        node,
10511                        analyzer,
10512                        visibility,
10513                        file,
10514                        arguments,
10515                        &reference_components,
10516                        target,
10517                    )
10518                    .unwrap_or(LexicalTypeResolution::Missing),
10519                }
10520            }
10521            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
10522        };
10523    }
10524    let resolution = if let Some(recovered_scope) = recovered_scope {
10525        resolve_type_components_lexically_at_for_target_with_recovered_scope(
10526            node,
10527            &reference_components,
10528            global,
10529            analyzer,
10530            visibility,
10531            ordinary_type_imports,
10532            file,
10533            source,
10534            target,
10535            true,
10536            recovered_scope,
10537        )
10538    } else {
10539        resolve_type_components_lexically_at_for_target_with_scope_cache(
10540            node,
10541            &reference_components,
10542            global,
10543            analyzer,
10544            visibility,
10545            ordinary_type_imports,
10546            file,
10547            source,
10548            target,
10549            true,
10550            scope_cache,
10551        )
10552    };
10553    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
10554        target_guided_qualified_namespace_function_type_resolution(
10555            node,
10556            &reference_components,
10557            global,
10558            analyzer,
10559            visibility,
10560            ordinary_type_imports,
10561            file,
10562            source,
10563            target,
10564        )
10565        .unwrap_or(resolution)
10566    } else {
10567        resolution
10568    };
10569    if !is_cpp_template_argument_type_leaf(node) {
10570        return resolution;
10571    }
10572
10573    // Preprocessor recovery can lift a member declaration out of its class
10574    // field list.  The unqualified template argument is then resolved from
10575    // the namespace only, even though the indexed enclosing callable still
10576    // identifies the class owner.  Retry this exact leaf against that
10577    // structured owner scope; ordinary type nodes must continue to use the
10578    // parser-derived lexical scope so unrelated same-spelled aliases remain
10579    // excluded.
10580    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10581        return resolution;
10582    };
10583    let namespace_scope = enclosing_namespace_components(node, source);
10584    if indexed_scope.len() <= namespace_scope.len() {
10585        return resolution;
10586    }
10587    let indexed = visibility.resolve_type_components_lexically_for_target(
10588        analyzer,
10589        file,
10590        &reference_components,
10591        global,
10592        &indexed_scope,
10593        target,
10594    );
10595    match indexed {
10596        LexicalTypeResolution::Resolved {
10597            ref unit,
10598            ref candidates,
10599            ..
10600        } if (same_visible_symbol(unit, target)
10601            || candidates
10602                .iter()
10603                .any(|candidate| same_visible_symbol(candidate, target)))
10604            && visibility
10605                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10606        {
10607            indexed
10608        }
10609        _ => resolution,
10610    }
10611}
10612
10613/// Recover the lexical namespace of an out-of-line namespace function whose
10614/// relative qualifier is reachable through a `using namespace` directive.
10615///
10616/// Per-file declaration extraction can attach `void schema::consume(...)` to
10617/// the first visible namespace prefix when more than one using-directive is
10618/// active. A matching visible free-function declaration still proves the
10619/// complete namespace. Target-guided inverse lookup may use that namespace to
10620/// retry a parameter or body type, but only when the target lives in the same
10621/// namespace and the declaration's callable arity matches the definition.
10622#[allow(clippy::too_many_arguments)]
10623fn target_guided_qualified_namespace_function_type_resolution(
10624    node: Node<'_>,
10625    components: &[String],
10626    global: bool,
10627    analyzer: &CppGraphSource<'_>,
10628    visibility: &VisibilityIndex<'_>,
10629    ordinary_type_imports: &OrdinaryTypeImportCell,
10630    file: &ProjectFile,
10631    source: &str,
10632    target: &CodeUnit,
10633) -> Option<LexicalTypeResolution> {
10634    let function_definition = std::iter::successors(Some(node), |current| current.parent())
10635        .find(|current| current.kind() == "function_definition")?;
10636    let function = function_definition_name_node(function_definition)?;
10637    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
10638    let target_namespace = target.package_name();
10639    if target_namespace.is_empty() {
10640        return None;
10641    }
10642    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10643        brokk_bifrost_core::analyzer::Language::Cpp,
10644        target_namespace,
10645    );
10646    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
10647    {
10648        return None;
10649    }
10650
10651    let function_name = node_text(function_terminal_node(function), source);
10652    let definition_unit = canonical_callable_at_node(
10653        function_definition,
10654        analyzer,
10655        visibility,
10656        file,
10657        source,
10658        TargetKind::FreeFunction,
10659    )?;
10660    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
10661    let declaration_proves_namespace = visibility
10662        .visible_identifier_candidates(file, function_name)
10663        .filter(|candidate| {
10664            candidate.is_function()
10665                && type_owner_of(analyzer, candidate).is_none()
10666                && candidate.package_name() == target_namespace
10667        })
10668        .any(|candidate| {
10669            cpp_callable_arity(analyzer, candidate)
10670                .is_none_or(|known| known.accepts(definition_arity))
10671        });
10672    if !declaration_proves_namespace {
10673        return None;
10674    }
10675
10676    let resolution = resolve_type_components_lexically_at_scoped(
10677        node,
10678        components,
10679        global,
10680        analyzer,
10681        visibility,
10682        ordinary_type_imports,
10683        file,
10684        source,
10685        Some(target),
10686        true,
10687        false,
10688        false,
10689        target_scope,
10690    );
10691    match &resolution {
10692        LexicalTypeResolution::Resolved {
10693            unit, candidates, ..
10694        } if (same_visible_symbol(unit, target)
10695            || candidates
10696                .iter()
10697                .any(|candidate| same_visible_symbol(candidate, target)))
10698            && visibility
10699                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10700        {
10701            Some(resolution)
10702        }
10703        LexicalTypeResolution::Resolved { .. }
10704        | LexicalTypeResolution::Ambiguous
10705        | LexicalTypeResolution::Missing => None,
10706    }
10707}
10708
10709#[allow(clippy::too_many_arguments)]
10710fn target_guided_malformed_template_alias_resolution(
10711    node: Node<'_>,
10712    analyzer: &CppGraphSource<'_>,
10713    visibility: &VisibilityIndex<'_>,
10714    file: &ProjectFile,
10715    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
10716    components: &[String],
10717    target: &CodeUnit,
10718) -> Option<LexicalTypeResolution> {
10719    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
10720        return None;
10721    }
10722
10723    let identifier = &components[0];
10724    let namespace =
10725        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
10726    let namespace_name = namespace.join("::");
10727    let candidates = visibility
10728        .visible_identifier_candidates(file, identifier)
10729        .filter(|candidate| {
10730            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
10731                && visibility.type_candidate_may_be_visible_before_reference(
10732                    analyzer,
10733                    file,
10734                    candidate,
10735                    node.start_byte(),
10736                )
10737        })
10738        .cloned()
10739        .collect::<Vec<_>>();
10740    let first = candidates.first()?;
10741    if !candidates
10742        .iter()
10743        .all(|candidate| same_logical_symbol(first, candidate))
10744        || !candidates.iter().all(|candidate| {
10745            visibility.template_alias_arguments_preserve_target(
10746                analyzer, file, candidate, arguments, target,
10747            )
10748        })
10749    {
10750        return None;
10751    }
10752
10753    let mut resolved_components = namespace;
10754    resolved_components.push(identifier.clone());
10755    Some(LexicalTypeResolution::Resolved {
10756        unit: target.clone(),
10757        components: resolved_components,
10758        candidates,
10759    })
10760}
10761
10762fn resolve_type_node_lexically_for_target_without_visibility(
10763    node: Node<'_>,
10764    analyzer: &CppGraphSource<'_>,
10765    visibility: &VisibilityIndex<'_>,
10766    file: &ProjectFile,
10767    source: &str,
10768    target: &CodeUnit,
10769) -> LexicalTypeResolution {
10770    let Some((components, global)) = type_reference_components(node, source) else {
10771        return LexicalTypeResolution::Missing;
10772    };
10773    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10774        node,
10775        analyzer,
10776        visibility,
10777        file,
10778        source,
10779        true,
10780        recovered_macro_decorated_declarator_type(node)
10781            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10782        &orphaned_namespace_scopes(visibility, file),
10783    ) {
10784        LexicalScopeResolution::Resolved(scope) => scope,
10785        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10786        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10787    };
10788    visibility.resolve_type_components_lexically_for_target(
10789        analyzer,
10790        file,
10791        &components,
10792        global,
10793        &lexical_scope,
10794        target,
10795    )
10796}
10797
10798fn type_node_has_exact_target_identity_without_visibility(
10799    node: Node<'_>,
10800    analyzer: &CppGraphSource<'_>,
10801    visibility: &VisibilityIndex<'_>,
10802    file: &ProjectFile,
10803    source: &str,
10804    target: &CodeUnit,
10805) -> bool {
10806    let Some((components, global)) = type_reference_components(node, source) else {
10807        return false;
10808    };
10809    let LexicalScopeResolution::Resolved(lexical_scope) =
10810        enclosing_lexical_scope_components_with_unresolved_owner(
10811            node,
10812            analyzer,
10813            visibility,
10814            file,
10815            source,
10816            true,
10817            recovered_macro_decorated_declarator_type(node)
10818                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10819            &orphaned_namespace_scopes(visibility, file),
10820        )
10821    else {
10822        return false;
10823    };
10824    let target_name = cpp_name_for(target);
10825    lexical_component_tiers(&components, global, &lexical_scope)
10826        .any(|qualified| qualified.join("::") == target_name)
10827}
10828
10829pub fn resolve_using_enum_declaration_owner(
10830    node: Node<'_>,
10831    analyzer: &CppGraphSource<'_>,
10832    visibility: &VisibilityIndex<'_>,
10833    ordinary_type_imports: &OrdinaryTypeImportCell,
10834    file: &ProjectFile,
10835    source: &str,
10836) -> LexicalTypeResolution {
10837    let Some(type_node) = using_enum_declaration_type_node(node) else {
10838        return LexicalTypeResolution::Missing;
10839    };
10840    let mut components = Vec::new();
10841    if append_cpp_name_components(type_node, source, &mut components).is_none()
10842        || components.is_empty()
10843    {
10844        return LexicalTypeResolution::Missing;
10845    }
10846    resolve_type_components_lexically_at(
10847        type_node,
10848        &components,
10849        is_globally_qualified_cpp_name(type_node),
10850        analyzer,
10851        visibility,
10852        ordinary_type_imports,
10853        file,
10854        source,
10855    )
10856}
10857
10858pub fn resolve_ordinary_using_declaration_owner(
10859    node: Node<'_>,
10860    analyzer: &CppGraphSource<'_>,
10861    visibility: &VisibilityIndex<'_>,
10862    file: &ProjectFile,
10863    source: &str,
10864) -> LexicalTypeResolution {
10865    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
10866        return LexicalTypeResolution::Missing;
10867    };
10868    let mut components = Vec::new();
10869    if append_cpp_name_components(type_node, source, &mut components).is_none()
10870        || components.len() < 2
10871    {
10872        return LexicalTypeResolution::Missing;
10873    }
10874    let lexical_scope =
10875        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
10876            LexicalScopeResolution::Resolved(scope) => scope,
10877            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10878            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10879        };
10880    visibility.resolve_type_components_lexically(
10881        analyzer,
10882        file,
10883        &components,
10884        is_globally_qualified_cpp_name(type_node),
10885        &lexical_scope,
10886    )
10887}
10888
10889pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10890    (node.kind() == "using_declaration"
10891        && (0..node.child_count()).any(|index| {
10892            node.child(index)
10893                .is_some_and(|child| child.kind() == "enum")
10894        }))
10895    .then(|| node.named_child(0))
10896    .flatten()
10897}
10898
10899pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10900    (node.kind() == "using_declaration"
10901        && using_enum_declaration_type_node(node).is_none()
10902        && using_namespace_directive_name_node(node).is_none())
10903    .then(|| node.named_child(0))
10904    .flatten()
10905}
10906
10907/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
10908/// undefined namespace-sentinel macro as a declaration whose type is the
10909/// all-caps sentinel and whose qualified declarator starts with a pseudo
10910/// `using` scope. The real imported name remains a structured qualified
10911/// identifier under that declarator. Recover only this exact CST envelope so
10912/// ordinary macro-decorated variables are not treated as imports.
10913fn recovered_macro_using_declaration_type_node<'tree>(
10914    node: Node<'tree>,
10915    source: &str,
10916) -> Option<(Node<'tree>, bool)> {
10917    if node.kind() != "declaration" {
10918        return None;
10919    }
10920    let macro_type = node.child_by_field_name("type")?;
10921    if macro_type.kind() != "type_identifier"
10922        || !cpp_export_macro_token(node_text(macro_type, source))
10923    {
10924        return None;
10925    }
10926    let declarator = node.child_by_field_name("declarator")?;
10927    if declarator.kind() != "qualified_identifier" {
10928        return None;
10929    }
10930    let scope = declarator.child_by_field_name("scope")?;
10931    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
10932        return None;
10933    }
10934    let target = declarator.child_by_field_name("name")?;
10935    let mut components = Vec::new();
10936    append_cpp_name_components(target, source, &mut components)?;
10937    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
10938}
10939
10940fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
10941    let is_directive = node.kind() == "using_directive"
10942        || (node.kind() == "using_declaration"
10943            && (0..node.child_count()).any(|index| {
10944                node.child(index)
10945                    .is_some_and(|child| child.kind() == "namespace")
10946            }));
10947    if !is_directive {
10948        return None;
10949    }
10950    node.child_by_field_name("name")
10951        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
10952}
10953
10954fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10955    let mut current = node.parent();
10956    while let Some(parent) = current {
10957        if matches!(
10958            parent.kind(),
10959            "compound_statement"
10960                | "function_definition"
10961                | "lambda_expression"
10962                | "for_statement"
10963                | "while_statement"
10964                | "if_statement"
10965                | "class_specifier"
10966                | "struct_specifier"
10967                | "union_specifier"
10968        ) {
10969            return None;
10970        }
10971        current = parent.parent();
10972    }
10973    Some(enclosing_namespace_components(node, source))
10974}
10975
10976fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
10977    let mut current = node.parent();
10978    while let Some(scope) = current {
10979        if matches!(
10980            scope.kind(),
10981            "compound_statement"
10982                | "declaration_list"
10983                | "field_declaration_list"
10984                | "translation_unit"
10985        ) {
10986            let mut depth = 0;
10987            let mut ancestor = scope.parent();
10988            while let Some(parent) = ancestor {
10989                depth += 1;
10990                ancestor = parent.parent();
10991            }
10992            return Some((
10993                scope.start_byte(),
10994                scope.end_byte(),
10995                depth,
10996                scope.kind() == "compound_statement",
10997            ));
10998        }
10999        current = scope.parent();
11000    }
11001    None
11002}
11003
11004/// Build the per-file structured using index for `file`.
11005///
11006/// The result is a pure function of the file's parsed content, which is what
11007/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11008/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11009/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11010pub fn build_source_using_index(
11011    cpp: &dyn CppSource,
11012    token: QueryToken<'_>,
11013    file: &ProjectFile,
11014) -> SourceUsingIndex {
11015    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11016        return SourceUsingIndex::default();
11017    };
11018    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11019}
11020
11021fn collect_source_using_index(
11022    cpp: &dyn CppSource,
11023    source_file: &ProjectFile,
11024    root: Node<'_>,
11025    source: &str,
11026) -> SourceUsingIndex {
11027    #[cfg(not(any(test, feature = "test-support")))]
11028    let _ = cpp;
11029    let mut index = SourceUsingIndex::default();
11030    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11031    let mut stack = vec![root];
11032    while let Some(node) = stack.pop() {
11033        let target = match node.kind() {
11034            "using_directive" | "using_declaration" => {
11035                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11036                    let mut namespace_components = Vec::new();
11037                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11038                        .map(|_| EffectiveUsingTarget::Namespace {
11039                            namespace_components,
11040                            global: is_globally_qualified_cpp_name(namespace_node),
11041                        })
11042                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11043                    let mut target_components = Vec::new();
11044                    (append_cpp_name_components(type_node, source, &mut target_components)
11045                        .is_some()
11046                        && target_components.len() >= 2)
11047                        .then(|| EffectiveUsingTarget::Ordinary {
11048                            name: target_components
11049                                .last()
11050                                .expect("ordinary using has a terminal component")
11051                                .clone(),
11052                            target_components,
11053                            global: is_globally_qualified_cpp_name(type_node),
11054                        })
11055                } else {
11056                    None
11057                }
11058            }
11059            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11060                |(type_node, global)| {
11061                    let mut target_components = Vec::new();
11062                    (append_cpp_name_components(type_node, source, &mut target_components)
11063                        .is_some()
11064                        && target_components.len() >= 2)
11065                        .then(|| EffectiveUsingTarget::Ordinary {
11066                            name: target_components
11067                                .last()
11068                                .expect("recovered ordinary using has a terminal component")
11069                                .clone(),
11070                            target_components,
11071                            global,
11072                        })
11073                },
11074            ),
11075            _ => None,
11076        };
11077        if let Some(target) = target {
11078            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11079            // searches from the root. The project index visits every AST node, but only these
11080            // structured using declarations need a guard environment. Keep the cheap target
11081            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11082            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11083            #[cfg(any(test, feature = "test-support"))]
11084            cpp.record_using_guard_context_inspection_for_test();
11085            let required_guards = if callable_preprocessor_context_is_visible(node, source) {
11086                Some(HashSet::default())
11087            } else {
11088                preprocessor_guard_environment(node, source)
11089            };
11090            let Some(required_guards) = required_guards else {
11091                let mut cursor = node.walk();
11092                stack.extend(node.children(&mut cursor));
11093                continue;
11094            };
11095            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11096                ordinary_using_scope(node)
11097            {
11098                let declaration_namespace = enclosing_namespace_components(node, source);
11099                let declaration_namespace = if declaration_namespace.is_empty() {
11100                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11101                        .unwrap_or(declaration_namespace)
11102                } else {
11103                    declaration_namespace
11104                };
11105                let namespace_scope = using_named_scope(node, source);
11106                let lexical_depth = declaration_namespace.len();
11107                let binding = OrdinaryTypeImport {
11108                    target,
11109                    source: source_file.clone(),
11110                    declaration_byte: node.end_byte(),
11111                    scope_start,
11112                    scope_end,
11113                    scope_depth,
11114                    block_scope,
11115                    lexical_depth,
11116                    declaration_namespace,
11117                    namespace_scope,
11118                    resolved_target_components: None,
11119                    required_guards,
11120                };
11121                match &binding.target {
11122                    EffectiveUsingTarget::Ordinary { name, .. } => index
11123                        .ordinary_by_name
11124                        .entry(name.clone())
11125                        .or_default()
11126                        .push(binding),
11127                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11128                }
11129            }
11130        }
11131        let mut cursor = node.walk();
11132        stack.extend(node.children(&mut cursor));
11133    }
11134    index
11135}
11136
11137struct OrphanedNamespaceEnvelope {
11138    body_end: usize,
11139    components: Vec<String>,
11140    class_names: HashSet<String>,
11141}
11142
11143/// Tree-sitter can terminate a namespace body at an object-like namespace
11144/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11145/// out-of-line definitions at translation-unit scope. A block-scoped using
11146/// declaration in one of those definitions still belongs to the namespace
11147/// selected by the malformed namespace envelope. Keep the envelope scan
11148/// source-local and reuse its structural ownership evidence for each using.
11149fn collect_orphaned_namespace_envelopes(
11150    root: Node<'_>,
11151    source: &str,
11152) -> Vec<OrphanedNamespaceEnvelope> {
11153    let mut envelopes = Vec::new();
11154    let mut stack = vec![root];
11155    while let Some(current) = stack.pop() {
11156        if current.kind() == "namespace_definition"
11157            && let Some(body) = current.child_by_field_name("body")
11158            && current.end_byte() == body.end_byte()
11159            && let Some(name) = current.child_by_field_name("name")
11160        {
11161            let mut components = enclosing_namespace_components(current, source);
11162            if append_cpp_name_components(name, source, &mut components).is_some()
11163                && !components.is_empty()
11164            {
11165                let mut class_names = HashSet::default();
11166                let mut body_stack = vec![body];
11167                while let Some(node) = body_stack.pop() {
11168                    if let Some(name) = orphaned_class_definition_name(node, source) {
11169                        class_names.insert(name);
11170                    }
11171                    let mut cursor = node.walk();
11172                    if node.kind() == "ERROR" {
11173                        body_stack.extend(node.children(&mut cursor));
11174                    } else {
11175                        body_stack.extend(node.named_children(&mut cursor));
11176                    }
11177                }
11178                envelopes.push(OrphanedNamespaceEnvelope {
11179                    body_end: body.end_byte(),
11180                    components,
11181                    class_names,
11182                });
11183            }
11184        }
11185        let mut cursor = current.walk();
11186        stack.extend(current.named_children(&mut cursor));
11187    }
11188    envelopes
11189}
11190
11191fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11192    if matches!(
11193        node.kind(),
11194        "class_specifier" | "struct_specifier" | "union_specifier"
11195    ) {
11196        let body = node.child_by_field_name("body")?;
11197        let name = node.child_by_field_name("name")?;
11198        return (!name.is_missing() && !body.is_missing())
11199            .then(|| node_text(name, source).to_string());
11200    }
11201    if node.kind() != "ERROR" {
11202        return None;
11203    }
11204
11205    // When an object-like namespace macro is parsed as a function definition,
11206    // tree-sitter can place the entire class declaration inside an ERROR node
11207    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11208    // fallback structural: accept only a named class-like keyword followed by
11209    // a real body, never an arbitrary identifier mentioned in the envelope.
11210    for index in 0..node.child_count() {
11211        let Some(keyword) = node.child(index) else {
11212            continue;
11213        };
11214        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11215            continue;
11216        }
11217        let mut name = None;
11218        for next_index in (index + 1)..node.child_count() {
11219            let Some(next) = node.child(next_index) else {
11220                continue;
11221            };
11222            if next.kind() == ";" {
11223                break;
11224            }
11225            if next.kind() == "{" {
11226                return name
11227                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11228                    .map(|name_node| node_text(name_node, source).to_string());
11229            }
11230            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11231                name = Some(next);
11232            }
11233        }
11234    }
11235    None
11236}
11237
11238fn recovered_orphaned_namespace_components(
11239    node: Node<'_>,
11240    source: &str,
11241    envelopes: &[OrphanedNamespaceEnvelope],
11242) -> Option<Vec<String>> {
11243    let owner_name = orphaned_using_owner_name(node, source)?;
11244    envelopes
11245        .iter()
11246        .filter(|envelope| {
11247            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11248        })
11249        .max_by_key(|envelope| envelope.body_end)
11250        .map(|envelope| envelope.components.clone())
11251}
11252
11253fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11254    let function = std::iter::successors(node.parent(), |current| current.parent())
11255        .find(|current| current.kind() == "function_definition")?;
11256    let owner = function_definition_owner_lookup_node(function)?;
11257    let scope = owner.child_by_field_name("scope")?;
11258    let mut components = Vec::new();
11259    append_cpp_name_components(scope, source, &mut components)?;
11260    // A qualified out-of-line member definition already carries its namespace
11261    // in the declarator scope (for example `foo::Widget::run`). The recovery
11262    // path is only for parser-orphaned top-level members whose owner scope
11263    // collapsed to the bare class name; requiring that shape prevents an
11264    // earlier, unrelated namespace/class from leaking into a global function's
11265    // using-directive lookup.
11266    if components.len() != 1 {
11267        return None;
11268    }
11269    components.pop()
11270}
11271
11272fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11273    let started = Instant::now();
11274    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11275    let source_files = visibility.all_visible_source_files();
11276    if report_stats {
11277        eprintln!(
11278            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11279            source_files.len()
11280        );
11281    }
11282    let mut project = ProjectUsingIndex::default();
11283    let mut ordinary_bindings = 0usize;
11284    for source_file in &source_files {
11285        // The per-file index is memoized on the analyzer, so assembling the
11286        // project index for a fresh `VisibilityIndex` copies bindings instead
11287        // of re-walking each file's AST (#1927).
11288        let source_index = visibility
11289            .cpp()
11290            .source_using_index(visibility.token(), source_file);
11291        for (name, bindings) in &source_index.ordinary_by_name {
11292            ordinary_bindings += bindings.len();
11293            project
11294                .ordinary_by_name
11295                .entry(name.clone())
11296                .or_default()
11297                .extend(bindings.iter().cloned());
11298        }
11299        project
11300            .directives
11301            .extend(source_index.directives.iter().cloned());
11302    }
11303    if report_stats {
11304        eprintln!(
11305            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11306            source_files.len(),
11307            project.ordinary_by_name.len(),
11308            ordinary_bindings,
11309            project.directives.len(),
11310            started.elapsed().as_millis(),
11311        );
11312    }
11313    project
11314}
11315
11316fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11317    visibility.project_using_index(|| build_project_using_index(visibility))
11318}
11319
11320/// Build the immutable project-wide using index before a parallel file scan.
11321///
11322/// Keeping the `OnceLock` publication here avoids making every scanner carry
11323/// an eager index, while callers that are about to fan out can prevent one
11324/// worker from doing the whole build as its peers wait on the lock.
11325pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11326    let _ = project_using_index(visibility);
11327}
11328
11329fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11330    let (components, global) = match &binding.target {
11331        EffectiveUsingTarget::Ordinary {
11332            target_components,
11333            global,
11334            ..
11335        } => (target_components, *global),
11336        EffectiveUsingTarget::Namespace {
11337            namespace_components,
11338            global,
11339        } => (namespace_components, *global),
11340    };
11341    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11342}
11343
11344fn using_binding_target_components_for_name(
11345    binding: &OrdinaryTypeImport,
11346    project: &ProjectUsingIndex,
11347    visibility: &VisibilityIndex<'_>,
11348    file: &ProjectFile,
11349    name: &str,
11350) -> Option<Vec<String>> {
11351    // Built once per call rather than per candidate: the filter runs over every
11352    // visible identifier of `name`, and the source is the same object each time.
11353    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
11354    let visible_candidates = visibility
11355        .visible_identifier_candidates(file, name)
11356        .filter(|candidate| {
11357            candidate.is_class()
11358                || declared_type_alias(&cpp_source, candidate)
11359                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
11360        })
11361        .collect::<Vec<_>>();
11362    if visible_candidates.is_empty() {
11363        return None;
11364    }
11365    match &binding.target {
11366        EffectiveUsingTarget::Ordinary {
11367            name: imported_name,
11368            ..
11369        } if imported_name == name => {
11370            effective_using_target_tiers(binding)
11371                .into_iter()
11372                .find(|qualified| {
11373                    let qualified_name = qualified.join("::");
11374                    visible_candidates
11375                        .iter()
11376                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
11377                })
11378        }
11379        EffectiveUsingTarget::Namespace { .. } => {
11380            visibility.note_using_namespace_lookup_for_test();
11381            let target_tiers = effective_using_target_tiers(binding);
11382            let resolved = target_tiers
11383                .iter()
11384                .find(|namespace_components| {
11385                    let namespace = namespace_components.join("::");
11386                    visible_candidates.iter().any(|candidate| {
11387                        visibility.note_using_name_candidate_inspection_for_test();
11388                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
11389                            candidate_namespace == namespace
11390                                || candidate_namespace.starts_with(&format!("{namespace}::"))
11391                        })
11392                    }) || project.directives.iter().any(|candidate| {
11393                        candidate.namespace_scope.as_deref()
11394                            == Some(namespace_components.as_slice())
11395                    }) || project
11396                        .ordinary_by_name
11397                        .values()
11398                        .flatten()
11399                        .any(|candidate| {
11400                            candidate.namespace_scope.as_deref()
11401                                == Some(namespace_components.as_slice())
11402                        })
11403                })
11404                .cloned();
11405            resolved.or_else(|| {
11406                // A sole lexical namespace tier is itself enough to retain the
11407                // directive. Candidate identity is resolved later, where
11408                // target guidance and macro-expanded owner names are available.
11409                // Dropping it here makes an unrelated same-terminal type hide
11410                // the actual namespace member before lookup can compare owners.
11411                (target_tiers.len() == 1)
11412                    .then(|| target_tiers.into_iter().next())
11413                    .flatten()
11414            })
11415        }
11416        EffectiveUsingTarget::Ordinary { .. } => None,
11417    }
11418}
11419
11420fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
11421    let start = activation.checked_sub(1)?;
11422    let mut node = root.descendant_for_byte_range(start, activation)?;
11423    while node.kind() != "preproc_include" {
11424        node = node.parent()?;
11425    }
11426    Some(node)
11427}
11428
11429fn project_using_bindings(
11430    binding: OrdinaryTypeImport,
11431    visibility: &VisibilityIndex<'_>,
11432    file: &ProjectFile,
11433    root: Node<'_>,
11434    source: &str,
11435) -> Vec<OrdinaryTypeImport> {
11436    if binding.source == *file {
11437        return vec![binding];
11438    }
11439    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
11440        return Vec::new();
11441    }
11442    visibility.note_using_donor_activation_for_test();
11443    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
11444        return Vec::new();
11445    };
11446    let projections = visibility
11447        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
11448        .map_or_else(
11449            || {
11450                visibility.conditional_include_projections_for_source(
11451                    file,
11452                    prepared.as_ref(),
11453                    &binding.source,
11454                )
11455            },
11456            |activation_byte| {
11457                Arc::from([ConditionalIncludeProjection {
11458                    activation_byte,
11459                    required_guards: HashSet::default(),
11460                    partial_guards: HashSet::default(),
11461                }])
11462            },
11463        );
11464    projections
11465        .iter()
11466        .cloned()
11467        .filter_map(|projection| {
11468            let required_guards =
11469                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
11470            let mut projected = binding.clone();
11471            projected.required_guards = required_guards;
11472            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
11473        })
11474        .collect()
11475}
11476
11477fn project_using_binding_at_activation(
11478    mut binding: OrdinaryTypeImport,
11479    activation: usize,
11480    root: Node<'_>,
11481    source: &str,
11482) -> Option<OrdinaryTypeImport> {
11483    let include = include_node_for_activation(root, activation)?;
11484    let include_namespace = enclosing_namespace_components(include, source);
11485    let mut declaration_namespace = include_namespace.clone();
11486    declaration_namespace.extend(binding.declaration_namespace);
11487    binding.declaration_namespace = declaration_namespace;
11488    binding.declaration_byte = activation;
11489    if let Some(prefix) = using_named_scope(include, source) {
11490        let mut projected = prefix;
11491        projected.extend(binding.namespace_scope.take().unwrap_or_default());
11492        binding.scope_depth = projected.len();
11493        binding.block_scope = false;
11494        binding.lexical_depth = projected.len();
11495        binding.namespace_scope = Some(projected);
11496        binding.scope_start = 0;
11497        binding.scope_end = usize::MAX;
11498        Some(binding)
11499    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
11500        binding.namespace_scope = None;
11501        binding.scope_start = start;
11502        binding.scope_end = end;
11503        binding.scope_depth = depth;
11504        binding.block_scope = block_scope;
11505        binding.lexical_depth = include_namespace.len();
11506        Some(binding)
11507    } else {
11508        None
11509    }
11510}
11511
11512/// `node` may be any node of `file`'s tree; the projection reaches the tree
11513/// root itself when it needs one. `Node::parent` re-descends from the root on
11514/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
11515/// call site cost a near-full-AST scan per reference on a large flat file
11516/// (#1927); a name with no candidate bindings never pays for it.
11517pub fn effective_using_bindings_for_name(
11518    visibility: &VisibilityIndex<'_>,
11519    imports: &OrdinaryTypeImportCell,
11520    file: &ProjectFile,
11521    node: Node<'_>,
11522    source: &str,
11523    name: &str,
11524) -> Arc<[OrdinaryTypeImport]> {
11525    imports
11526        .projection_cell(name)
11527        .get_or_init(|| {
11528            let project = project_using_index(visibility);
11529            let name_bindings = project.ordinary_by_name.get(name);
11530            if name_bindings.is_none() && project.directives.is_empty() {
11531                return Arc::from(Vec::new());
11532            }
11533            let root = root_node(node);
11534            let mut projected = Vec::new();
11535            for binding in name_bindings
11536                .into_iter()
11537                .flatten()
11538                .chain(project.directives.iter())
11539            {
11540                if !visibility.source_is_visible(file, &binding.source) {
11541                    continue;
11542                }
11543                let target_components = using_binding_target_components_for_name(
11544                    binding, project, visibility, file, name,
11545                )
11546                .or_else(|| match &binding.target {
11547                    EffectiveUsingTarget::Ordinary {
11548                        name: imported_name,
11549                        target_components,
11550                        ..
11551                    } if imported_name == name => Some(target_components.clone()),
11552                    EffectiveUsingTarget::Ordinary { .. }
11553                    | EffectiveUsingTarget::Namespace { .. } => None,
11554                });
11555                let Some(target_components) = target_components else {
11556                    continue;
11557                };
11558                let mut binding = binding.clone();
11559                binding.resolved_target_components = Some(target_components);
11560                projected.extend(project_using_bindings(
11561                    binding, visibility, file, root, source,
11562                ));
11563            }
11564            Arc::from(projected)
11565        })
11566        .clone()
11567}
11568
11569pub fn initialized_ordinary_type_imports(
11570    root: Node<'_>,
11571    analyzer: &CppGraphSource<'_>,
11572    visibility: &VisibilityIndex<'_>,
11573    file: &ProjectFile,
11574    source: &str,
11575) -> OrdinaryTypeImportCell {
11576    let cell = visibility.ordinary_type_import_cell(file);
11577    let _ = (root, analyzer, source);
11578    cell
11579}
11580
11581fn root_node(mut node: Node<'_>) -> Node<'_> {
11582    while let Some(parent) = node.parent() {
11583        node = parent;
11584    }
11585    node
11586}
11587
11588/// `reference_guards` is the reference node's guard environment, computed once
11589/// by the caller and shared across every binding: recomputing it per binding
11590/// repeated a full ancestor climb whose every `Node::parent` step re-descends
11591/// from the root (#1927).
11592fn effective_using_binding_active(
11593    binding: &OrdinaryTypeImport,
11594    node: Node<'_>,
11595    lexical_scope: &[String],
11596    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11597    visibility: &VisibilityIndex<'_>,
11598    file: &ProjectFile,
11599) -> bool {
11600    effective_using_binding_guards_active(
11601        binding,
11602        node.start_byte(),
11603        reference_guards,
11604        visibility,
11605        file,
11606    ) && binding.namespace_scope.as_ref().map_or_else(
11607        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11608        |namespace| lexical_scope.starts_with(namespace),
11609    )
11610}
11611
11612fn effective_using_binding_guards_active(
11613    binding: &OrdinaryTypeImport,
11614    reference_byte: usize,
11615    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11616    visibility: &VisibilityIndex<'_>,
11617    file: &ProjectFile,
11618) -> bool {
11619    binding.declaration_byte <= reference_byte
11620        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
11621        && visibility.preprocessor_guards_stable_between(
11622            file,
11623            binding.declaration_byte,
11624            reference_byte,
11625            &binding.required_guards,
11626        )
11627}
11628
11629fn effective_using_binding_guards_compatible(
11630    binding: &OrdinaryTypeImport,
11631    reference_byte: usize,
11632    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11633    visibility: &VisibilityIndex<'_>,
11634    file: &ProjectFile,
11635) -> bool {
11636    binding.source != *file
11637        && !binding.required_guards.is_empty()
11638        && binding.declaration_byte <= reference_byte
11639        && reference_guards.is_some_and(|active| {
11640            !binding.required_guards.is_subset(active)
11641                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
11642        })
11643        && visibility.preprocessor_guards_stable_between(
11644            file,
11645            binding.declaration_byte,
11646            reference_byte,
11647            &binding.required_guards,
11648        )
11649}
11650
11651#[allow(clippy::too_many_arguments)]
11652fn binding_type_candidates(
11653    binding: &OrdinaryTypeImport,
11654    active_bindings: &[&OrdinaryTypeImport],
11655    analyzer: &CppGraphSource<'_>,
11656    visibility: &VisibilityIndex<'_>,
11657    file: &ProjectFile,
11658    name: &str,
11659    direct_target: Option<&CodeUnit>,
11660    reference_byte: usize,
11661) -> Vec<(CodeUnit, Vec<String>)> {
11662    let Some(qualified) = binding.resolved_target_components.clone() else {
11663        return Vec::new();
11664    };
11665    let mut targets = Vec::new();
11666    match binding.target {
11667        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
11668        EffectiveUsingTarget::Namespace { .. } => {
11669            let mut stack = vec![qualified];
11670            let mut visited = HashSet::default();
11671            while let Some(namespace) = stack.pop() {
11672                if !visited.insert(namespace.clone()) {
11673                    continue;
11674                }
11675                let mut target = namespace.clone();
11676                target.push(name.to_string());
11677                targets.push(target);
11678                stack.extend(active_bindings.iter().filter_map(|candidate| {
11679                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
11680                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
11681                    .then(|| candidate.resolved_target_components.clone())
11682                    .flatten()
11683                }));
11684            }
11685        }
11686    }
11687    targets
11688        .into_iter()
11689        .flat_map(|target| {
11690            let mut candidates = visibility
11691                .visible_identifier_candidates(file, name)
11692                .filter(|candidate| {
11693                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
11694                        && type_candidate_matches_lookup_components(
11695                            analyzer,
11696                            visibility,
11697                            file,
11698                            candidate,
11699                            reference_byte,
11700                            &target,
11701                        )
11702                })
11703                .cloned()
11704                .collect::<Vec<_>>();
11705            if candidates.is_empty()
11706                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11707                && let Some(target_unit) = direct_target
11708            {
11709                let expanded_target_name = macro_expanded_cpp_name_components(
11710                    visibility,
11711                    file,
11712                    target_unit,
11713                    reference_byte,
11714                );
11715                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
11716                    && expanded_target_name == target
11717                    && visibility.external_type_candidate_visible_at(
11718                        file,
11719                        target_unit,
11720                        reference_byte,
11721                    )
11722                {
11723                    candidates.push(target_unit.clone());
11724                }
11725            }
11726            if candidates.is_empty()
11727                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11728                && let Some(target_unit) = direct_target
11729            {
11730                let visible_types = visibility
11731                    .visible_identifier_candidates(file, name)
11732                    .filter(|candidate| {
11733                        candidate.is_class() || declared_type_alias(analyzer, candidate)
11734                    })
11735                    .collect::<Vec<_>>();
11736                let uniquely_names_target = !visible_types.is_empty()
11737                    && visible_types
11738                        .iter()
11739                        .all(|candidate| same_visible_symbol(candidate, target_unit));
11740                if uniquely_names_target {
11741                    candidates.extend(visible_types.into_iter().cloned());
11742                }
11743            }
11744            candidates
11745                .into_iter()
11746                .map(move |candidate| (candidate, target.clone()))
11747        })
11748        .collect()
11749}
11750
11751fn macro_expanded_cpp_name_components(
11752    visibility: &VisibilityIndex<'_>,
11753    file: &ProjectFile,
11754    unit: &CodeUnit,
11755    reference_byte: usize,
11756) -> Vec<String> {
11757    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11758        brokk_bifrost_core::analyzer::Language::Cpp,
11759        &cpp_name_for(unit),
11760    )
11761    .into_iter()
11762    .flat_map(|component| {
11763        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11764    })
11765    .collect()
11766}
11767
11768fn macro_expanded_cpp_name_component(
11769    visibility: &VisibilityIndex<'_>,
11770    file: &ProjectFile,
11771    component: String,
11772    reference_byte: usize,
11773) -> Vec<String> {
11774    let Some(replacement) =
11775        visibility.object_macro_replacement_at(file, &component, reference_byte)
11776    else {
11777        return vec![component];
11778    };
11779    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11780        brokk_bifrost_core::analyzer::Language::Cpp,
11781        &replacement,
11782    );
11783    if expanded.is_empty() {
11784        vec![component]
11785    } else {
11786        expanded
11787    }
11788}
11789
11790/// Whether a visible type has the requested qualified spelling.
11791///
11792/// Members of an inline namespace are also members of its enclosing namespace,
11793/// so an ordinary using-declaration may legally omit the inline component. The
11794/// stored FQ name retains that component to keep declarations distinct. Recover
11795/// the omitted spellings from the candidate declaration's namespace ancestors,
11796/// using CST `inline` tokens rather than guessing from names.
11797fn type_candidate_matches_lookup_components(
11798    analyzer: &CppGraphSource<'_>,
11799    visibility: &VisibilityIndex<'_>,
11800    file: &ProjectFile,
11801    candidate: &CodeUnit,
11802    reference_byte: usize,
11803    target: &[String],
11804) -> bool {
11805    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11806    if expanded == target {
11807        return true;
11808    }
11809    let Some(cpp) = analyzer.cpp else {
11810        return false;
11811    };
11812    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11813        return false;
11814    };
11815    let root = prepared.tree().root_node();
11816    for range in analyzer.ranges(candidate) {
11817        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11818        else {
11819            continue;
11820        };
11821        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11822        loop {
11823            if current.kind() == "namespace_definition"
11824                && let Some(name) = current.child_by_field_name("name")
11825            {
11826                let mut components = Vec::new();
11827                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11828                    && !components.is_empty()
11829                {
11830                    let inline = (0..current.child_count())
11831                        .filter_map(|index| current.child(index))
11832                        .any(|child| !child.is_named() && child.kind() == "inline");
11833                    namespaces.push((components, inline));
11834                }
11835            }
11836            let Some(parent) = current.parent() else {
11837                break;
11838            };
11839            current = parent;
11840        }
11841        namespaces.reverse();
11842        let mut namespace_components = Vec::new();
11843        let mut inline_indexes = HashSet::default();
11844        for (components, inline) in namespaces {
11845            for component in components {
11846                let expanded_component =
11847                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11848                if inline {
11849                    inline_indexes.extend(
11850                        namespace_components.len()
11851                            ..namespace_components.len() + expanded_component.len(),
11852                    );
11853                }
11854                namespace_components.extend(expanded_component);
11855            }
11856        }
11857        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11858            continue;
11859        }
11860        // Each inline namespace component can be present or elided. Compare
11861        // those alternatives as a small dynamic program instead of generating
11862        // every subset of a deeply nested inline-namespace chain.
11863        let mut reachable = vec![false; target.len() + 1];
11864        reachable[0] = true;
11865        for (index, component) in expanded.iter().enumerate() {
11866            let mut next = vec![false; target.len() + 1];
11867            for (target_index, reached) in reachable.iter().copied().enumerate() {
11868                if !reached {
11869                    continue;
11870                }
11871                if inline_indexes.contains(&index) {
11872                    next[target_index] = true;
11873                }
11874                if target
11875                    .get(target_index)
11876                    .is_some_and(|target_component| target_component == component)
11877                {
11878                    next[target_index + 1] = true;
11879                }
11880            }
11881            reachable = next;
11882        }
11883        if reachable[target.len()] {
11884            return true;
11885        }
11886    }
11887    false
11888}
11889
11890#[allow(clippy::too_many_arguments)]
11891fn resolved_type_import(
11892    candidates: Vec<(CodeUnit, Vec<String>)>,
11893    lexical_depth: usize,
11894    is_direct: bool,
11895    analyzer: &CppGraphSource<'_>,
11896    visibility: &VisibilityIndex<'_>,
11897    file: &ProjectFile,
11898    direct_target: Option<&CodeUnit>,
11899) -> OrdinaryTypeImportResolution {
11900    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
11901    for candidate in candidates {
11902        if !logical
11903            .iter()
11904            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
11905        {
11906            logical.push(candidate);
11907        }
11908    }
11909    let selected = match logical.as_slice() {
11910        [] => return OrdinaryTypeImportResolution::Missing,
11911        [only] => only,
11912        // Several declarations of one FQN in one file are configuration
11913        // spellings of one entity, not competing types (#1845): the imported
11914        // name is unambiguous, only the branch that supplies it depends on the
11915        // build.
11916        several => {
11917            let units = several
11918                .iter()
11919                .map(|(unit, _)| unit)
11920                .collect::<Vec<&CodeUnit>>();
11921            let Some(spelling) = direct_target.and_then(|target| {
11922                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
11923            }) else {
11924                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
11925            };
11926            several
11927                .iter()
11928                .find(|(unit, _)| same_symbol(unit, spelling))
11929                .expect("the selected spelling is one of the imported candidates")
11930        }
11931    };
11932    OrdinaryTypeImportResolution::Resolved {
11933        target: selected.0.clone(),
11934        target_components: selected.1.clone(),
11935        lexical_depth,
11936        is_direct,
11937    }
11938}
11939
11940#[allow(clippy::too_many_arguments)]
11941fn ordinary_type_import_resolution(
11942    node: Node<'_>,
11943    components: &[String],
11944    global: bool,
11945    analyzer: &CppGraphSource<'_>,
11946    visibility: &VisibilityIndex<'_>,
11947    imports: &OrdinaryTypeImportCell,
11948    file: &ProjectFile,
11949    source: &str,
11950    lexical_scope: &[String],
11951    direct_target: Option<&CodeUnit>,
11952) -> OrdinaryTypeImportResolution {
11953    if global || components.len() != 1 {
11954        return OrdinaryTypeImportResolution::Missing;
11955    }
11956    let name = &components[0];
11957    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11958    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
11959    // step re-descends from the root (#1927). A name with no bindings needs
11960    // none of it, and one environment serves every binding of the reference.
11961    if bindings.is_empty() {
11962        return OrdinaryTypeImportResolution::Missing;
11963    }
11964    let reference_guards = preprocessor_guard_environment(node, source);
11965    let active = bindings
11966        .iter()
11967        .filter(|binding| {
11968            effective_using_binding_active(
11969                binding,
11970                node,
11971                lexical_scope,
11972                reference_guards.as_ref(),
11973                visibility,
11974                file,
11975            )
11976        })
11977        .collect::<Vec<_>>();
11978    let transitive = bindings
11979        .iter()
11980        .filter(|binding| {
11981            effective_using_binding_guards_active(
11982                binding,
11983                node.start_byte(),
11984                reference_guards.as_ref(),
11985                visibility,
11986                file,
11987            ) && (binding.namespace_scope.is_some()
11988                || (binding.scope_start <= node.start_byte()
11989                    && node.end_byte() <= binding.scope_end))
11990        })
11991        .collect::<Vec<_>>();
11992    ordinary_type_import_resolution_for_bindings(
11993        node,
11994        name,
11995        analyzer,
11996        visibility,
11997        file,
11998        lexical_scope,
11999        direct_target,
12000        &active,
12001        &transitive,
12002    )
12003}
12004
12005#[allow(clippy::too_many_arguments)]
12006fn compatible_foreign_type_import_resolution(
12007    node: Node<'_>,
12008    components: &[String],
12009    global: bool,
12010    analyzer: &CppGraphSource<'_>,
12011    visibility: &VisibilityIndex<'_>,
12012    imports: &OrdinaryTypeImportCell,
12013    file: &ProjectFile,
12014    source: &str,
12015    lexical_scope: &[String],
12016    direct_target: Option<&CodeUnit>,
12017) -> OrdinaryTypeImportResolution {
12018    if global || components.len() != 1 {
12019        return OrdinaryTypeImportResolution::Missing;
12020    }
12021    let name = &components[0];
12022    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12023    if bindings.is_empty() {
12024        return OrdinaryTypeImportResolution::Missing;
12025    }
12026    let reference_guards = preprocessor_guard_environment(node, source);
12027    let compatible = bindings
12028        .iter()
12029        .filter(|binding| {
12030            effective_using_binding_guards_compatible(
12031                binding,
12032                node.start_byte(),
12033                reference_guards.as_ref(),
12034                visibility,
12035                file,
12036            ) && binding.namespace_scope.as_ref().map_or_else(
12037                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12038                |namespace| lexical_scope.starts_with(namespace),
12039            )
12040        })
12041        .collect::<Vec<_>>();
12042    let transitive = bindings
12043        .iter()
12044        .filter(|binding| {
12045            effective_using_binding_guards_compatible(
12046                binding,
12047                node.start_byte(),
12048                reference_guards.as_ref(),
12049                visibility,
12050                file,
12051            ) && (binding.namespace_scope.is_some()
12052                || (binding.scope_start <= node.start_byte()
12053                    && node.end_byte() <= binding.scope_end))
12054        })
12055        .collect::<Vec<_>>();
12056    ordinary_type_import_resolution_for_bindings(
12057        node,
12058        name,
12059        analyzer,
12060        visibility,
12061        file,
12062        lexical_scope,
12063        direct_target,
12064        &compatible,
12065        &transitive,
12066    )
12067}
12068
12069#[allow(clippy::too_many_arguments)]
12070fn ordinary_type_import_resolution_for_bindings(
12071    node: Node<'_>,
12072    name: &str,
12073    analyzer: &CppGraphSource<'_>,
12074    visibility: &VisibilityIndex<'_>,
12075    file: &ProjectFile,
12076    lexical_scope: &[String],
12077    direct_target: Option<&CodeUnit>,
12078    active: &[&OrdinaryTypeImport],
12079    transitive: &[&OrdinaryTypeImport],
12080) -> OrdinaryTypeImportResolution {
12081    let mut concrete_depths = active
12082        .iter()
12083        .filter(|binding| binding.namespace_scope.is_none())
12084        .map(|binding| binding.scope_depth)
12085        .collect::<Vec<_>>();
12086    concrete_depths.sort_unstable();
12087    concrete_depths.dedup();
12088    for depth in concrete_depths.into_iter().rev() {
12089        let at_tier = active
12090            .iter()
12091            .copied()
12092            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12093        let direct = at_tier
12094            .clone()
12095            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12096            .flat_map(|binding| {
12097                binding_type_candidates(
12098                    binding,
12099                    transitive,
12100                    analyzer,
12101                    visibility,
12102                    file,
12103                    name,
12104                    direct_target,
12105                    node.start_byte(),
12106                )
12107            })
12108            .collect::<Vec<_>>();
12109        if !direct.is_empty() {
12110            return resolved_type_import(
12111                direct,
12112                lexical_scope.len(),
12113                true,
12114                analyzer,
12115                visibility,
12116                file,
12117                direct_target,
12118            );
12119        }
12120        let directives = at_tier
12121            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12122            .flat_map(|binding| {
12123                binding_type_candidates(
12124                    binding,
12125                    transitive,
12126                    analyzer,
12127                    visibility,
12128                    file,
12129                    name,
12130                    direct_target,
12131                    node.start_byte(),
12132                )
12133            })
12134            .collect::<Vec<_>>();
12135        if !directives.is_empty() {
12136            return resolved_type_import(
12137                directives,
12138                lexical_scope.len(),
12139                false,
12140                analyzer,
12141                visibility,
12142                file,
12143                direct_target,
12144            );
12145        }
12146    }
12147    for prefix_len in (0..=lexical_scope.len()).rev() {
12148        let tier = &lexical_scope[..prefix_len];
12149        let at_tier = active
12150            .iter()
12151            .copied()
12152            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12153        let direct = at_tier
12154            .clone()
12155            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12156            .flat_map(|binding| {
12157                binding_type_candidates(
12158                    binding,
12159                    transitive,
12160                    analyzer,
12161                    visibility,
12162                    file,
12163                    name,
12164                    direct_target,
12165                    node.start_byte(),
12166                )
12167            })
12168            .collect::<Vec<_>>();
12169        if !direct.is_empty() {
12170            return resolved_type_import(
12171                direct,
12172                prefix_len,
12173                true,
12174                analyzer,
12175                visibility,
12176                file,
12177                direct_target,
12178            );
12179        }
12180        let directives = at_tier
12181            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12182            .flat_map(|binding| {
12183                binding_type_candidates(
12184                    binding,
12185                    transitive,
12186                    analyzer,
12187                    visibility,
12188                    file,
12189                    name,
12190                    direct_target,
12191                    node.start_byte(),
12192                )
12193            })
12194            .collect::<Vec<_>>();
12195        if !directives.is_empty() {
12196            return resolved_type_import(
12197                directives,
12198                prefix_len,
12199                false,
12200                analyzer,
12201                visibility,
12202                file,
12203                direct_target,
12204            );
12205        }
12206    }
12207    OrdinaryTypeImportResolution::Missing
12208}
12209
12210#[allow(clippy::too_many_arguments)]
12211pub fn resolve_type_components_lexically_at(
12212    node: Node<'_>,
12213    components: &[String],
12214    global: bool,
12215    analyzer: &CppGraphSource<'_>,
12216    visibility: &VisibilityIndex<'_>,
12217    ordinary_type_imports: &OrdinaryTypeImportCell,
12218    file: &ProjectFile,
12219    source: &str,
12220) -> LexicalTypeResolution {
12221    resolve_type_components_lexically_at_inner(
12222        node,
12223        components,
12224        global,
12225        analyzer,
12226        visibility,
12227        ordinary_type_imports,
12228        file,
12229        source,
12230        None,
12231        false,
12232        false,
12233        false,
12234        None,
12235    )
12236}
12237
12238/// Resolve a type at its lexical reference site while retaining the identity
12239/// of an alias that C++ lookup selects.
12240///
12241/// Forward navigation uses the selected spelling as its destination, whereas
12242/// graph attribution normally canonicalizes an alias to its target. Both
12243/// surfaces must still apply the same ordinary using-declarations, declaration
12244/// order, guard state, and lexical-depth precedence.
12245#[allow(clippy::too_many_arguments)]
12246pub fn resolve_type_components_lexically_at_preserving_alias(
12247    node: Node<'_>,
12248    components: &[String],
12249    global: bool,
12250    analyzer: &CppGraphSource<'_>,
12251    visibility: &VisibilityIndex<'_>,
12252    file: &ProjectFile,
12253    source: &str,
12254) -> LexicalTypeResolution {
12255    let ordinary_type_imports =
12256        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12257    resolve_type_components_lexically_at_inner(
12258        node,
12259        components,
12260        global,
12261        analyzer,
12262        visibility,
12263        &ordinary_type_imports,
12264        file,
12265        source,
12266        None,
12267        false,
12268        true,
12269        true,
12270        None,
12271    )
12272}
12273
12274#[allow(clippy::too_many_arguments)]
12275fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12276    node: Node<'_>,
12277    components: &[String],
12278    global: bool,
12279    analyzer: &CppGraphSource<'_>,
12280    visibility: &VisibilityIndex<'_>,
12281    ordinary_type_imports: &OrdinaryTypeImportCell,
12282    file: &ProjectFile,
12283    source: &str,
12284    scope_cache: Option<&LexicalScopeCache>,
12285) -> LexicalTypeResolution {
12286    resolve_type_components_lexically_at_inner(
12287        node,
12288        components,
12289        global,
12290        analyzer,
12291        visibility,
12292        ordinary_type_imports,
12293        file,
12294        source,
12295        None,
12296        false,
12297        true,
12298        false,
12299        scope_cache,
12300    )
12301}
12302
12303#[allow(clippy::too_many_arguments)]
12304fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12305    node: Node<'_>,
12306    components: &[String],
12307    global: bool,
12308    analyzer: &CppGraphSource<'_>,
12309    visibility: &VisibilityIndex<'_>,
12310    ordinary_type_imports: &OrdinaryTypeImportCell,
12311    file: &ProjectFile,
12312    source: &str,
12313    target: &CodeUnit,
12314    apply_structured_prefilter: bool,
12315    scope_cache: Option<&LexicalScopeCache>,
12316) -> LexicalTypeResolution {
12317    resolve_type_components_lexically_at_inner(
12318        node,
12319        components,
12320        global,
12321        analyzer,
12322        visibility,
12323        ordinary_type_imports,
12324        file,
12325        source,
12326        Some(target),
12327        apply_structured_prefilter,
12328        false,
12329        false,
12330        scope_cache,
12331    )
12332}
12333
12334#[allow(clippy::too_many_arguments)]
12335fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12336    node: Node<'_>,
12337    components: &[String],
12338    global: bool,
12339    analyzer: &CppGraphSource<'_>,
12340    visibility: &VisibilityIndex<'_>,
12341    ordinary_type_imports: &OrdinaryTypeImportCell,
12342    file: &ProjectFile,
12343    source: &str,
12344    recovered_scope: &[String],
12345) -> LexicalTypeResolution {
12346    resolve_type_components_in_authoritative_scope(
12347        node,
12348        components,
12349        global,
12350        analyzer,
12351        visibility,
12352        ordinary_type_imports,
12353        file,
12354        source,
12355        None,
12356        false,
12357        true,
12358        false,
12359        recovered_scope.to_vec(),
12360    )
12361}
12362
12363#[allow(clippy::too_many_arguments)]
12364fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12365    node: Node<'_>,
12366    components: &[String],
12367    global: bool,
12368    analyzer: &CppGraphSource<'_>,
12369    visibility: &VisibilityIndex<'_>,
12370    ordinary_type_imports: &OrdinaryTypeImportCell,
12371    file: &ProjectFile,
12372    source: &str,
12373    target: &CodeUnit,
12374    apply_structured_prefilter: bool,
12375    recovered_scope: &[String],
12376) -> LexicalTypeResolution {
12377    resolve_type_components_in_authoritative_scope(
12378        node,
12379        components,
12380        global,
12381        analyzer,
12382        visibility,
12383        ordinary_type_imports,
12384        file,
12385        source,
12386        Some(target),
12387        apply_structured_prefilter,
12388        false,
12389        false,
12390        recovered_scope.to_vec(),
12391    )
12392}
12393
12394#[allow(clippy::too_many_arguments)]
12395fn resolve_type_components_lexically_at_inner(
12396    node: Node<'_>,
12397    components: &[String],
12398    global: bool,
12399    analyzer: &CppGraphSource<'_>,
12400    visibility: &VisibilityIndex<'_>,
12401    ordinary_type_imports: &OrdinaryTypeImportCell,
12402    file: &ProjectFile,
12403    source: &str,
12404    direct_target: Option<&CodeUnit>,
12405    apply_structured_prefilter: bool,
12406    preserve_alias: bool,
12407    allow_compatible_foreign_import: bool,
12408    scope_cache: Option<&LexicalScopeCache>,
12409) -> LexicalTypeResolution {
12410    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12411    let lexical_scope_started = Instant::now();
12412    if report_stats {
12413        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
12414    }
12415    let lexical_scope = if global {
12416        Vec::new()
12417    } else {
12418        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
12419            node,
12420            analyzer,
12421            visibility,
12422            file,
12423            source,
12424            true,
12425            recovered_macro_decorated_declarator_type(node)
12426                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
12427            scope_cache,
12428        ) {
12429            LexicalScopeResolution::Resolved(scope) => scope,
12430            LexicalScopeResolution::Ambiguous => {
12431                if report_stats {
12432                    eprintln!(
12433                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
12434                        lexical_scope_started.elapsed().as_millis(),
12435                    );
12436                }
12437                return LexicalTypeResolution::Ambiguous;
12438            }
12439            LexicalScopeResolution::Missing => {
12440                if report_stats {
12441                    eprintln!(
12442                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
12443                        lexical_scope_started.elapsed().as_millis(),
12444                    );
12445                }
12446                return LexicalTypeResolution::Missing;
12447            }
12448        }
12449    };
12450    if report_stats {
12451        eprintln!(
12452            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
12453            lexical_scope.len(),
12454            lexical_scope_started.elapsed().as_millis(),
12455        );
12456    }
12457    resolve_type_components_lexically_at_scoped(
12458        node,
12459        components,
12460        global,
12461        analyzer,
12462        visibility,
12463        ordinary_type_imports,
12464        file,
12465        source,
12466        direct_target,
12467        apply_structured_prefilter,
12468        preserve_alias,
12469        allow_compatible_foreign_import,
12470        lexical_scope,
12471    )
12472}
12473
12474#[allow(clippy::too_many_arguments)]
12475fn resolve_type_components_lexically_at_scoped(
12476    node: Node<'_>,
12477    components: &[String],
12478    global: bool,
12479    analyzer: &CppGraphSource<'_>,
12480    visibility: &VisibilityIndex<'_>,
12481    ordinary_type_imports: &OrdinaryTypeImportCell,
12482    file: &ProjectFile,
12483    source: &str,
12484    direct_target: Option<&CodeUnit>,
12485    apply_structured_prefilter: bool,
12486    preserve_alias: bool,
12487    allow_compatible_foreign_import: bool,
12488    mut lexical_scope: Vec<String>,
12489) -> LexicalTypeResolution {
12490    if !global
12491        && components.len() == 1
12492        // The two constant-time conditions run before the ancestor climb: each
12493        // `Node::parent` step re-descends from the root (#1927).
12494        && let Some(target) = direct_target
12495        && lexical_scope
12496            .last()
12497            .is_none_or(|last| last != &components[0])
12498        // A recovered class may contain a real member function nested inside
12499        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
12500        // XMLConstHandle). The nearest function_definition is then the member
12501        // itself, so inspect the complete ancestor chain.
12502        && has_malformed_wrapper_function_definition_ancestor(node)
12503        && let Some(indexed_namespace) =
12504            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
12505        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
12506    {
12507        lexical_scope = indexed_namespace;
12508    }
12509    resolve_type_components_in_authoritative_scope(
12510        node,
12511        components,
12512        global,
12513        analyzer,
12514        visibility,
12515        ordinary_type_imports,
12516        file,
12517        source,
12518        direct_target,
12519        apply_structured_prefilter,
12520        preserve_alias,
12521        allow_compatible_foreign_import,
12522        lexical_scope,
12523    )
12524}
12525
12526/// Resolve within a scope already proven by recovered syntax.
12527///
12528/// Unlike parser-derived scope, this scope must not be replaced with the
12529/// queried target's namespace: doing so would let target guidance override a
12530/// nearer declaration represented by the recovered syntax.
12531#[allow(clippy::too_many_arguments)]
12532fn resolve_type_components_in_authoritative_scope(
12533    node: Node<'_>,
12534    components: &[String],
12535    global: bool,
12536    analyzer: &CppGraphSource<'_>,
12537    visibility: &VisibilityIndex<'_>,
12538    ordinary_type_imports: &OrdinaryTypeImportCell,
12539    file: &ProjectFile,
12540    source: &str,
12541    direct_target: Option<&CodeUnit>,
12542    apply_structured_prefilter: bool,
12543    preserve_alias: bool,
12544    allow_compatible_foreign_import: bool,
12545    lexical_scope: Vec<String>,
12546) -> LexicalTypeResolution {
12547    if apply_structured_prefilter
12548        && direct_target.is_some()
12549        && !preserve_alias
12550        && !global
12551        && components.len() == 1
12552        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
12553    {
12554        return LexicalTypeResolution::Missing;
12555    }
12556    // A recovered macro-prefixed return type can share its global spelling
12557    // with aliases from mutually exclusive included headers. C++ lookup uses
12558    // the declaration physically present earlier in this file; the visibility
12559    // index deliberately retains every configuration alternative. Restore
12560    // that precedence only for the exact recovered scope and lexical tier.
12561    if !global
12562        && components.len() == 1
12563        && recovered_macro_decorated_type_node(node).is_some()
12564        && let Some(resolution) = recovered_same_file_type_alias_resolution(
12565            node,
12566            components,
12567            analyzer,
12568            visibility,
12569            file,
12570            direct_target,
12571            &lexical_scope,
12572        )
12573    {
12574        return resolution;
12575    }
12576    if apply_structured_prefilter
12577        && let Some(target) = direct_target
12578        && !preserve_alias
12579        && !visibility.structured_type_reference_may_resolve_to_target(
12580            analyzer,
12581            file,
12582            components,
12583            global,
12584            &lexical_scope,
12585            target,
12586        )
12587    {
12588        return LexicalTypeResolution::Missing;
12589    }
12590    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12591    let lexical_normal_started = Instant::now();
12592    if report_stats {
12593        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
12594    }
12595    let normal = if preserve_alias {
12596        visibility.resolve_type_components_lexically_for_forward(
12597            analyzer,
12598            file,
12599            components,
12600            global,
12601            &lexical_scope,
12602        )
12603    } else {
12604        direct_target.map_or_else(
12605            || {
12606                visibility.resolve_type_components_lexically(
12607                    analyzer,
12608                    file,
12609                    components,
12610                    global,
12611                    &lexical_scope,
12612                )
12613            },
12614            |target| {
12615                visibility.resolve_type_components_lexically_for_target(
12616                    analyzer,
12617                    file,
12618                    components,
12619                    global,
12620                    &lexical_scope,
12621                    target,
12622                )
12623            },
12624        )
12625    };
12626    if report_stats {
12627        eprintln!(
12628            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
12629            lexical_normal_started.elapsed().as_millis(),
12630        );
12631    }
12632    let normal = match normal {
12633        LexicalTypeResolution::Resolved { ref unit, .. }
12634            if !visibility
12635                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
12636                && !direct_target.is_some_and(|target| {
12637                    let candidate_refs = visibility
12638                        .visible_identifier_candidates(file, target.identifier())
12639                        .collect::<Vec<_>>();
12640                    visibility.c_tag_declaration_family_matches_target(
12641                        analyzer,
12642                        file,
12643                        &candidate_refs,
12644                        target,
12645                    )
12646                }) =>
12647        {
12648            LexicalTypeResolution::Missing
12649        }
12650        resolution => resolution,
12651    };
12652    let normal_depth = match &normal {
12653        LexicalTypeResolution::Resolved { components, .. } => {
12654            Some(components.len().saturating_sub(1))
12655        }
12656        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
12657    };
12658    // Ordinary using-declarations participate in unqualified lookup at their
12659    // lexical scope. They therefore replace the resolver's terminal/global
12660    // fallback at the same or a shallower depth. A declaration in a more deeply
12661    // nested named scope is the closer lexical result and remains authoritative.
12662    // Ambiguous imports fail closed unless such a closer declaration exists.
12663    let ordinary_import_started = Instant::now();
12664    if report_stats {
12665        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
12666    }
12667    let ordinary_import_resolution = ordinary_type_import_resolution(
12668        node,
12669        components,
12670        global,
12671        analyzer,
12672        visibility,
12673        ordinary_type_imports,
12674        file,
12675        source,
12676        &lexical_scope,
12677        direct_target,
12678    );
12679    if report_stats {
12680        eprintln!(
12681            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
12682            ordinary_import_started.elapsed().as_millis(),
12683        );
12684    }
12685    let resolution = match ordinary_import_resolution {
12686        OrdinaryTypeImportResolution::Missing => normal,
12687        OrdinaryTypeImportResolution::Resolved {
12688            lexical_depth,
12689            is_direct,
12690            ..
12691        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
12692            || normal_depth.is_some_and(|depth| {
12693                depth > lexical_depth || (!is_direct && depth == lexical_depth)
12694            }) =>
12695        {
12696            normal
12697        }
12698        OrdinaryTypeImportResolution::Resolved {
12699            target,
12700            target_components,
12701            ..
12702        } => visibility.resolve_imported_type_candidate(
12703            analyzer,
12704            file,
12705            &target,
12706            &target_components,
12707            direct_target,
12708            preserve_alias,
12709        ),
12710        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
12711            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
12712        {
12713            normal
12714        }
12715        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12716    };
12717    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12718        return resolution;
12719    }
12720
12721    // A foreign header can contribute an exact using binding from a build
12722    // configuration that is compatible with, but not implied by, the current
12723    // reference. Forward navigation has no "unproven" result channel, so it
12724    // may still navigate through one unique structured import after ordinary
12725    // lookup misses. Inverse attribution keeps using its target-guided path
12726    // and records the same evidence as unproven (#940).
12727    match compatible_foreign_type_import_resolution(
12728        node,
12729        components,
12730        global,
12731        analyzer,
12732        visibility,
12733        ordinary_type_imports,
12734        file,
12735        source,
12736        &lexical_scope,
12737        None,
12738    ) {
12739        OrdinaryTypeImportResolution::Missing => resolution,
12740        OrdinaryTypeImportResolution::Resolved {
12741            target,
12742            target_components,
12743            ..
12744        } => visibility.resolve_imported_type_candidate(
12745            analyzer,
12746            file,
12747            &target,
12748            &target_components,
12749            None,
12750            true,
12751        ),
12752        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12753    }
12754}
12755
12756fn recovered_same_file_type_alias_resolution(
12757    node: Node<'_>,
12758    components: &[String],
12759    analyzer: &CppGraphSource<'_>,
12760    visibility: &VisibilityIndex<'_>,
12761    file: &ProjectFile,
12762    direct_target: Option<&CodeUnit>,
12763    lexical_scope: &[String],
12764) -> Option<LexicalTypeResolution> {
12765    debug_assert_eq!(components.len(), 1);
12766    debug_assert!(recovered_macro_decorated_type_node(node).is_some());
12767    let alias_provider = analyzer.type_alias_provider()?;
12768    for qualified in lexical_component_tiers(components, false, lexical_scope) {
12769        let candidates = visibility
12770            .visible_identifier_candidates(file, &components[0])
12771            .filter(|candidate| {
12772                candidate.source() == file
12773                    && canonical_cpp_scope_components(candidate) == qualified
12774                    && visibility
12775                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12776            })
12777            .collect::<Vec<_>>();
12778        if candidates.is_empty() {
12779            continue;
12780        }
12781        if candidates
12782            .iter()
12783            .any(|candidate| !alias_provider.is_type_alias(candidate))
12784        {
12785            return None;
12786        }
12787        let unit = if let Some(target) = direct_target {
12788            visibility.unique_type_candidate_preserving_target(
12789                analyzer,
12790                file,
12791                &candidates,
12792                target,
12793            )?
12794        } else {
12795            let first = candidates[0];
12796            if candidates
12797                .iter()
12798                .any(|candidate| !same_visible_symbol(candidate, first))
12799            {
12800                return None;
12801            }
12802            first.clone()
12803        };
12804        return Some(LexicalTypeResolution::Resolved {
12805            unit,
12806            components: qualified,
12807            candidates: candidates.into_iter().cloned().collect(),
12808        });
12809    }
12810    None
12811}
12812
12813fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12814    matches!(
12815        structured_owner_context_resolution(node, ctx),
12816        StructuredOwnerContextResolution::SelfTarget
12817            | StructuredOwnerContextResolution::InheritedTarget
12818    )
12819}
12820
12821/// A bare/`this->` member call whose name resolves, through the enclosing class's base
12822/// hierarchy or through a lexically enclosing class, to the target member declared on
12823/// another owner. This is a genuine external usage of that owner's member rather than a
12824/// same-type self call.
12825fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12826    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
12827        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
12828    }) else {
12829        return matches!(
12830            structured_owner_context_resolution(node, ctx),
12831            StructuredOwnerContextResolution::InheritedTarget
12832        );
12833    };
12834    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12835        return false;
12836    };
12837    let chain = structured_enclosing_owner_chain(node, ctx);
12838    let Some(innermost) = chain.first() else {
12839        return false;
12840    };
12841    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12842        return false;
12843    }
12844    let Some(arity) = ctx
12845        .visibility
12846        .call_arity_evidence(ctx.file, call, ctx.source)
12847        .exact()
12848    else {
12849        return false;
12850    };
12851    for enclosing_owner in &chain {
12852        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12853            return true;
12854        }
12855        match resolve_declaring_callable_owner(
12856            &ctx.analyzer,
12857            ctx.visibility,
12858            ctx.file,
12859            cached_declaring_member_owner(enclosing_owner, ctx),
12860            &ctx.spec.member_name,
12861            arity,
12862        ) {
12863            EnclosingMemberOwnerResolution::Owner(owner) => {
12864                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12865            }
12866            EnclosingMemberOwnerResolution::Ambiguous => return false,
12867            // The name is not declared in this class or its bases, so C++
12868            // unqualified lookup continues in the next enclosing class (#3095).
12869            EnclosingMemberOwnerResolution::Missing => {}
12870        }
12871    }
12872    false
12873}
12874
12875fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12876    matches!(
12877        structured_owner_context_resolution(node, ctx),
12878        StructuredOwnerContextResolution::NonTarget
12879    )
12880}
12881
12882fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12883    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12884        return false;
12885    };
12886    let mut current = ctx.ancestry.parent(node);
12887    while let Some(parent) = current {
12888        if parent.kind() == "function_definition" {
12889            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
12890                return false;
12891            };
12892            if let Some(owners) = out_of_line_member_definition_owner(
12893                &ctx.analyzer,
12894                ctx.visibility,
12895                ctx.file,
12896                ctx.source,
12897                owner_lookup,
12898            ) && let Some((_, owner)) = owners.innermost()
12899            {
12900                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
12901            }
12902            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
12903                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12904            }
12905            return false;
12906        }
12907        current = ctx.ancestry.parent(parent);
12908    }
12909    false
12910}
12911
12912#[derive(Clone, Copy)]
12913enum StructuredOwnerContextResolution {
12914    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
12915    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
12916    SelfTarget,
12917    /// The enclosing class does not declare the member; lookup reaches the target owner
12918    /// through a base of that class, or through a lexically enclosing class (a nested
12919    /// class calling the outer class's member). Either way the reference is a genuine
12920    /// external usage OF that owner's member (e.g. `Derived` calling inherited
12921    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
12922    InheritedTarget,
12923    NonTarget,
12924    Ambiguous,
12925    Missing,
12926}
12927
12928fn structured_owner_context_resolution(
12929    node: Node<'_>,
12930    ctx: &ScanCtx<'_>,
12931) -> StructuredOwnerContextResolution {
12932    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12933        return StructuredOwnerContextResolution::Missing;
12934    };
12935    let chain = structured_enclosing_owner_chain(node, ctx);
12936    let Some(innermost) = chain.first() else {
12937        return StructuredOwnerContextResolution::Missing;
12938    };
12939    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12940        return StructuredOwnerContextResolution::SelfTarget;
12941    }
12942    // Unqualified lookup searches the enclosing class and its bases, then each lexically
12943    // enclosing class outward, and stops at the first scope that declares the name (#3095).
12944    // Only the immediately enclosing class makes a reference a same-type self call; any
12945    // other owner it reaches is a genuine usage of that owner's member.
12946    for enclosing_owner in &chain {
12947        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12948            return StructuredOwnerContextResolution::InheritedTarget;
12949        }
12950        match cached_declaring_member_owner(enclosing_owner, ctx) {
12951            EnclosingMemberOwnerResolution::Owner(owner)
12952                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
12953            {
12954                return StructuredOwnerContextResolution::InheritedTarget;
12955            }
12956            EnclosingMemberOwnerResolution::Owner(_) => {
12957                return StructuredOwnerContextResolution::NonTarget;
12958            }
12959            EnclosingMemberOwnerResolution::Ambiguous => {
12960                return StructuredOwnerContextResolution::Ambiguous;
12961            }
12962            EnclosingMemberOwnerResolution::Missing => {}
12963        }
12964    }
12965    StructuredOwnerContextResolution::Missing
12966}
12967
12968fn cached_declaring_member_owner(
12969    receiver_owner: &CodeUnit,
12970    ctx: &ScanCtx<'_>,
12971) -> EnclosingMemberOwnerResolution {
12972    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
12973        return cached;
12974    }
12975    let resolved = resolve_declaring_member_owner(
12976        &ctx.analyzer,
12977        ctx.visibility,
12978        ctx.file,
12979        receiver_owner,
12980        &ctx.spec.member_name,
12981    );
12982    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
12983        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
12984    } else {
12985        resolved
12986    };
12987    ctx.member_owner_cache
12988        .borrow_mut()
12989        .insert(receiver_owner.clone(), resolved.clone());
12990    resolved
12991}
12992
12993/// Recover a member's declaring owner when parser recovery omitted its
12994/// in-class declaration but retained an out-of-line definition. Ordinary
12995/// visible-member lookup runs first. The structured definition index then
12996/// supplies the missing member fact at each hierarchy level, so an indexed
12997/// derived override still hides the queried base member and distinct base
12998/// paths still fail closed.
12999fn indexed_declaring_owner_for_recovered_member(
13000    receiver_owner: &CodeUnit,
13001    ctx: &ScanCtx<'_>,
13002) -> EnclosingMemberOwnerResolution {
13003    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13004        return EnclosingMemberOwnerResolution::Missing;
13005    };
13006    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13007        return EnclosingMemberOwnerResolution::Missing;
13008    }
13009    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13010        return EnclosingMemberOwnerResolution::Missing;
13011    };
13012    let Some(receiver_owner) =
13013        ctx.visibility
13014            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13015    else {
13016        return EnclosingMemberOwnerResolution::Ambiguous;
13017    };
13018    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13019        ctx.visibility
13020            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13021    }) else {
13022        return EnclosingMemberOwnerResolution::Missing;
13023    };
13024
13025    let owner_declares_member = |owner: &CodeUnit| {
13026        if same_visible_symbol(owner, &target_owner) {
13027            return true;
13028        }
13029        let mut member_fq = owner.fq().clone();
13030        member_fq.push(
13031            ctx.spec
13032                .target
13033                .fq()
13034                .last()
13035                .expect("a method target has a terminal member segment"),
13036        );
13037        ctx.analyzer
13038            .definitions(&member_fq.display(segment_interner()))
13039            .any(|child| child.is_function())
13040    };
13041    if owner_declares_member(&receiver_owner) {
13042        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13043    }
13044
13045    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
13046    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13047    let mut declaring_owner = None;
13048    while let Some(raw_owner) = stack.pop() {
13049        let Some(owner) =
13050            ctx.visibility
13051                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13052        else {
13053            return EnclosingMemberOwnerResolution::Ambiguous;
13054        };
13055        let propagated = propagated_counts.entry(owner.clone()).or_default();
13056        if *propagated == 2 {
13057            continue;
13058        }
13059        *propagated += 1;
13060        if owner_declares_member(&owner) {
13061            if declaring_owner.is_some() {
13062                return EnclosingMemberOwnerResolution::Ambiguous;
13063            }
13064            declaring_owner = Some(owner);
13065            continue;
13066        }
13067        stack.extend(hierarchy.get_direct_ancestors(&owner));
13068    }
13069    declaring_owner
13070        .map(EnclosingMemberOwnerResolution::Owner)
13071        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13072}
13073
13074/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13075/// lookup searches each in turn before it reaches the enclosing namespaces.
13076fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13077    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13078        return Vec::new();
13079    };
13080    // The innermost owner is whatever enclosing-owner resolution produced, so
13081    // it is kept as given; only the walk outward is filtered to classes.
13082    let mut chain = vec![innermost.clone()];
13083    chain.extend(
13084        brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(innermost, |unit| {
13085            ctx.analyzer.parent_of(unit)
13086        })
13087        .skip(1)
13088        .take_while(CodeUnit::is_class),
13089    );
13090    chain
13091}
13092
13093fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13094    // Declaration recovery can index the true class/member ranges even when
13095    // the original error tree wraps that region in a bogus function. Prefer
13096    // the analyzer's exact enclosing-owner graph at the reference byte before
13097    // interpreting such a wrapper as a real callable owner.
13098    if (has_recovered_class_shape_ancestor(node)
13099        || has_malformed_wrapper_function_definition_ancestor(node))
13100        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13101    {
13102        return Some(owner);
13103    }
13104    let mut current = ctx.ancestry.parent(node);
13105    while let Some(parent) = current {
13106        if parent.kind() == "function_definition" {
13107            let owner_lookup = function_definition_owner_lookup_node(parent);
13108            if let Some(owner_lookup) = owner_lookup
13109                && let Some(owners) = out_of_line_member_definition_owner(
13110                    &ctx.analyzer,
13111                    ctx.visibility,
13112                    ctx.file,
13113                    ctx.source,
13114                    owner_lookup,
13115                )
13116                && let Some((_, owner)) = owners.innermost()
13117            {
13118                return Some(owner.clone());
13119            }
13120            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13121                return Some(owner);
13122            }
13123            if let Some(owner) = enclosing_context(parent, ctx)
13124                .owner
13125                .filter(|owner| owner.is_class())
13126            {
13127                return Some(owner);
13128            }
13129            if let Some(owner_lookup) = owner_lookup
13130                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13131            {
13132                return Some(owner);
13133            }
13134            break;
13135        }
13136        current = ctx.ancestry.parent(parent);
13137    }
13138    enclosing_context(node, ctx)
13139        .owner
13140        .filter(|owner| owner.is_class())
13141}
13142
13143fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13144    let target_owner = ctx.spec.owner.as_ref()?;
13145    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13146    let owner_name = owner_components.last()?;
13147    let mut candidates = Vec::new();
13148    for candidate in ctx
13149        .visibility
13150        .visible_identifier_candidates(ctx.file, owner_name)
13151        .filter(|candidate| candidate.is_class())
13152    {
13153        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13154            brokk_bifrost_core::analyzer::Language::Cpp,
13155            &cpp_name_for(candidate),
13156        );
13157        if !components.ends_with(&owner_components)
13158            || candidates
13159                .iter()
13160                .any(|existing| same_logical_symbol(existing, candidate))
13161        {
13162            continue;
13163        }
13164        candidates.push(candidate.clone());
13165    }
13166    let [candidate] = candidates.as_slice() else {
13167        return None;
13168    };
13169    (same_logical_symbol(candidate, target_owner)
13170        && target_group_contains_owner_peer(candidate, ctx))
13171    .then(|| candidate.clone())
13172}