Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

1use crate::call_match::{
2    CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_forwarding_call_argument,
3    cpp_literal_arg_type, cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6    CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7    cpp_sentinel_recovered_scope_for_node, enclosing_cpp_namespace_nodes,
8    is_recovered_exported_class_base_type_node, node_text, normalize_cpp_whitespace,
9    recovered_macro_return_type_node,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
13use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
14use crate::graph::hits::{
15    enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
16    push_declared_reference_hit, push_definition_hit, push_hit, push_recovered_definition_hit,
17    push_recursive_reference_hit, push_reference_hit_range, push_self_receiver_hit, push_type_hit,
18    push_type_hit_range, push_unproven_definition_hit, push_unproven_hit,
19    push_unproven_reference_hit_range,
20};
21use crate::graph::namespace_alias::NamespaceQualifierBinding;
22use crate::graph::resolver::*;
23use crate::graph::syntax::{
24    function_macro_replacement_span, object_macro_replacement_type_references,
25    qualified_callable_value, receiver_is_self_like,
26};
27use crate::graph_support::CppSource;
28use brokk_bifrost_core::analyzer::fq_name::segment_interner;
29use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
30use brokk_bifrost_core::analyzer::query_token::QueryToken;
31use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_is_within;
32use brokk_bifrost_core::analyzer::tree_walk::{
33    ParentIndex, WalkControl, children_iter, first_named_child_of_kind, parent_if_field,
34    push_named_children_reversed, root_node, walk_named_tree_preorder,
35};
36use brokk_bifrost_core::analyzer::usages::common::same_node;
37use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
38use brokk_bifrost_core::analyzer::usages::local_inference::{
39    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
40};
41use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
42use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
43use brokk_bifrost_core::hash::{HashMap, HashSet};
44#[cfg(any(test, feature = "test-support"))]
45use std::cell::Cell;
46use std::cell::RefCell;
47use std::collections::BTreeSet;
48use std::sync::Arc;
49use std::time::Instant;
50use tree_sitter::Node;
51
52#[cfg(any(test, feature = "test-support"))]
53thread_local! {
54    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
55    static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
56}
57
58#[cfg(any(test, feature = "test-support"))]
59pub fn reset_type_reference_candidate_scan_count_for_test() {
60    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
61}
62
63#[cfg(any(test, feature = "test-support"))]
64pub fn type_reference_candidate_scan_count_for_test() -> usize {
65    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
66}
67
68pub struct ScanState<'a> {
69    pub max_usages: usize,
70    pub hits: &'a mut BTreeSet<UsageHit>,
71    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
72    pub raw_match_count: &'a mut usize,
73    pub limit_exceeded: &'a mut bool,
74}
75
76pub struct ScanCtx<'a> {
77    pub analyzer: CppGraphSource<'a>,
78    pub visibility: &'a VisibilityIndex<'a>,
79    pub file: &'a ProjectFile,
80    pub source: &'a str,
81    /// The parent of every node in the file being scanned, recorded by one
82    /// downward pass when the scan starts. `Node::parent` recovers a parent by
83    /// re-descending from the root, so an ancestor climb is quadratic in depth
84    /// and the scan climbs from more than a dozen call sites per visited node
85    /// (#1927, #3097).
86    pub ancestry: ParentIndex<'a>,
87    ordinary_type_imports: OrdinaryTypeImportCell,
88    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
89    class_ranges: Option<&'a ClassRangeIndex>,
90    pub line_starts: &'a [usize],
91    pub spec: &'a TargetSpec,
92    pub target_group: &'a HashSet<CodeUnit>,
93    pub has_proven_visible_type_target: bool,
94    uses_c_semantics: bool,
95    type_reference_component_names: HashSet<String>,
96    pub target_declaration_ranges: Vec<Range>,
97    target_macro_declaration_bytes: Vec<usize>,
98    pub bindings: LocalInferenceEngine<CppScanBinding>,
99    local_shadows: LocalInferenceEngine<()>,
100    using_enum_owners: ScopedUsingEnumOwners,
101    semantic_using_enum_owners: SemanticUsingEnumOwners,
102    needs_using_enum_member_resolution: bool,
103    pub hits: &'a mut BTreeSet<UsageHit>,
104    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
105    pub raw_match_count: &'a mut usize,
106    pub max_usages: usize,
107    pub external_hit_count: usize,
108    pub limit_exceeded: &'a mut bool,
109    pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
110    pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
111    lexical_scope_cache: LexicalScopeCache,
112    lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
113    member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
114    global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
115    receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
116}
117
118impl ScanCtx<'_> {
119    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
120        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
121    }
122}
123
124#[derive(Clone, Default)]
125pub struct EnclosingContext {
126    pub enclosing: Option<CodeUnit>,
127    pub owner: Option<CodeUnit>,
128}
129
130pub fn prepare_file(
131    cpp: &dyn CppSource,
132    token: QueryToken<'_>,
133    file: &ProjectFile,
134) -> Option<Arc<PreparedSyntaxTree>> {
135    cpp.prepared_syntax(token, file)
136}
137
138#[allow(clippy::too_many_arguments)]
139pub fn scan_prepared_file(
140    analyzer: &CppGraphSource<'_>,
141    visibility: &VisibilityIndex<'_>,
142    file: &ProjectFile,
143    prepared: &PreparedSyntaxTree,
144    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
145    class_ranges: Option<&ClassRangeIndex>,
146    spec: &TargetSpec,
147    target_group: &HashSet<CodeUnit>,
148    state: &mut ScanState<'_>,
149) {
150    if *state.limit_exceeded {
151        return;
152    }
153    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
154    let has_proven_visible_type_target = spec.kind == TargetKind::Type
155        && (target_group.iter().any(|target| {
156            same_logical_symbol(target, &spec.target)
157                && visibility.is_physically_visible(file, target)
158        }) || {
159            let candidates = visibility
160                .visible_identifier_candidates(file, spec.target.identifier())
161                .collect::<Vec<_>>();
162            visibility.c_tag_declaration_family_matches_target(
163                analyzer,
164                file,
165                &candidates,
166                &spec.target,
167            )
168        });
169    if spec.kind == TargetKind::Type
170        && !has_proven_visible_type_target
171        && visibility
172            .visible_identifier_candidates(file, spec.target.identifier())
173            .any(|candidate| {
174                candidate != &spec.target
175                    && !target_group.contains(candidate)
176                    && same_logical_symbol(candidate, &spec.target)
177                    && visibility.is_physically_visible(file, candidate)
178                    && !visibility.c_tag_declaration_family_matches_target(
179                        analyzer,
180                        file,
181                        std::slice::from_ref(&candidate),
182                        &spec.target,
183                    )
184            })
185    {
186        return;
187    }
188    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
189        analyzer.ranges(&spec.target)
190    } else {
191        Vec::new()
192    };
193    let target_declaration_ranges = if spec.kind == TargetKind::Type {
194        target_group
195            .iter()
196            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
197            .flat_map(|target| analyzer.ranges(target))
198            .collect()
199    } else if spec.target.source() == file {
200        if spec.kind == TargetKind::Macro {
201            macro_target_declaration_ranges.clone()
202        } else {
203            analyzer.ranges(&spec.target)
204        }
205    } else {
206        Vec::new()
207    };
208    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
209        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
210    } else {
211        Vec::new()
212    };
213    let type_reference_component_names = if spec.kind == TargetKind::Type {
214        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
215    } else {
216        HashSet::default()
217    };
218    if spec.kind == TargetKind::Type
219        && !file_may_reference_type_target(
220            prepared.tree().root_node(),
221            prepared.source(),
222            analyzer,
223            visibility,
224            file,
225            &spec.target,
226            &type_reference_component_names,
227        )
228    {
229        return;
230    }
231    let ordinary_type_imports = initialized_ordinary_type_imports(
232        prepared.tree().root_node(),
233        analyzer,
234        visibility,
235        file,
236        prepared.source(),
237    );
238    let external_hit_count = state
239        .hits
240        .iter()
241        .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
242        .count();
243    let mut ctx = ScanCtx {
244        analyzer: *analyzer,
245        visibility,
246        file,
247        source: prepared.source(),
248        ancestry: ParentIndex::new(prepared.tree().root_node()),
249        ordinary_type_imports,
250        recovered_sentinel_classes,
251        class_ranges,
252        line_starts: prepared.line_starts(),
253        spec,
254        target_group,
255        has_proven_visible_type_target,
256        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
257        type_reference_component_names,
258        target_declaration_ranges,
259        target_macro_declaration_bytes,
260        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
261        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
262        using_enum_owners: ScopedUsingEnumOwners::new(),
263        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
264        needs_using_enum_member_resolution,
265        hits: state.hits,
266        unproven_hits: state.unproven_hits,
267        raw_match_count: state.raw_match_count,
268        max_usages: state.max_usages,
269        external_hit_count,
270        limit_exceeded: state.limit_exceeded,
271        enclosing_cache: RefCell::new(HashMap::default()),
272        enclosing_owner_cache: RefCell::new(HashMap::default()),
273        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
274        lexical_free_function_cache: RefCell::new(HashMap::default()),
275        member_owner_cache: RefCell::new(HashMap::default()),
276        global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
277        receiver_canonical_type_cache: RefCell::new(HashMap::default()),
278    };
279    if needs_using_enum_member_resolution {
280        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
281    }
282    if spec.kind == TargetKind::Macro {
283        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
284        return;
285    }
286    scan_node(prepared.tree().root_node(), &mut ctx);
287}
288
289/// Scan a macro target without maintaining the declaration and lexical state
290/// required by ordinary C and C++ symbols. Macro activation comes entirely
291/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
292/// node makes generated tables needlessly expensive.
293fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
294    walk_named_tree_preorder(root, true, |node| {
295        if *ctx.limit_exceeded {
296            return WalkControl::Break;
297        }
298        maybe_record_macro_hit(node, ctx);
299        WalkControl::Continue
300    });
301}
302
303enum UsingEnumDeclarationScope {
304    Block,
305    Class(CodeUnit),
306    Namespace(Vec<String>),
307    UnsupportedClass,
308}
309
310fn resolved_using_enum_declaration(
311    node: Node<'_>,
312    ctx: &ScanCtx<'_>,
313) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
314    let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
315        node,
316        &ctx.analyzer,
317        ctx.visibility,
318        &ctx.ordinary_type_imports,
319        ctx.file,
320        ctx.source,
321    ) else {
322        return None;
323    };
324    Some((unit, using_enum_declaration_scope(node, ctx)))
325}
326
327fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
328    let mut current = ctx.ancestry.parent(node);
329    while let Some(parent) = current {
330        if matches!(
331            parent.kind(),
332            "compound_statement"
333                | "function_definition"
334                | "lambda_expression"
335                | "for_statement"
336                | "while_statement"
337                | "if_statement"
338        ) {
339            return UsingEnumDeclarationScope::Block;
340        }
341        if matches!(
342            parent.kind(),
343            "class_specifier" | "struct_specifier" | "union_specifier"
344        ) {
345            let resolution = enclosing_lexical_scope_components(
346                node,
347                &ctx.analyzer,
348                ctx.visibility,
349                ctx.file,
350                ctx.source,
351            );
352            if let LexicalScopeResolution::Resolved(components) = resolution
353                && let LexicalTypeResolution::Resolved { unit, .. } =
354                    ctx.visibility.resolve_type_components_lexically(
355                        &ctx.analyzer,
356                        ctx.file,
357                        &components,
358                        true,
359                        &[],
360                    )
361            {
362                return UsingEnumDeclarationScope::Class(unit);
363            }
364            return UsingEnumDeclarationScope::UnsupportedClass;
365        }
366        current = ctx.ancestry.parent(parent);
367    }
368    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
369}
370
371fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
372    let mut stack = vec![root];
373    while let Some(node) = stack.pop() {
374        if node.kind() == "using_declaration"
375            && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
376        {
377            match scope {
378                UsingEnumDeclarationScope::Block => {}
379                UsingEnumDeclarationScope::Class(class) => {
380                    ctx.semantic_using_enum_owners.import_class(class, unit);
381                }
382                UsingEnumDeclarationScope::Namespace(namespace) => {
383                    ctx.semantic_using_enum_owners.import_namespace(
384                        namespace,
385                        node.start_byte(),
386                        unit,
387                    );
388                }
389                UsingEnumDeclarationScope::UnsupportedClass => {}
390            }
391        }
392        push_named_children_reversed(node, &mut stack);
393    }
394}
395
396fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
397    struct Frame<'tree> {
398        node: Node<'tree>,
399        next_child: Option<Node<'tree>>,
400        previous_child: Option<Node<'tree>>,
401        entered: bool,
402        enters_scope: bool,
403        enters_using_enum_scope: bool,
404        fractured_function_scope: bool,
405    }
406    impl<'tree> Frame<'tree> {
407        fn new(node: Node<'tree>) -> Self {
408            Self {
409                node,
410                next_child: node.named_child(0),
411                previous_child: None,
412                entered: false,
413                enters_scope: false,
414                enters_using_enum_scope: false,
415                fractured_function_scope: false,
416            }
417        }
418    }
419    if *ctx.limit_exceeded {
420        return;
421    }
422    let mut stack = vec![Frame::new(node)];
423    while let Some(current) = stack.last_mut() {
424        let node = current.node;
425        let translation_unit = node.kind() == "translation_unit";
426        if !current.entered {
427            current.entered = true;
428            current.enters_scope = matches!(
429                node.kind(),
430                "compound_statement"
431                    | "function_definition"
432                    | "lambda_expression"
433                    | "for_statement"
434                    | "for_range_loop"
435                    | "while_statement"
436                    | "if_statement"
437            );
438            current.enters_using_enum_scope = ctx.needs_using_enum_member_resolution
439                && (current.enters_scope
440                    || matches!(
441                        node.kind(),
442                        "namespace_definition"
443                            | "class_specifier"
444                            | "struct_specifier"
445                            | "union_specifier"
446                    ));
447            if current.enters_scope {
448                ctx.bindings.enter_scope();
449                ctx.local_shadows.enter_scope();
450            }
451            if current.enters_using_enum_scope {
452                ctx.using_enum_owners.enter_scope();
453            }
454            seed_declarations(node, ctx);
455            maybe_record_hit(node, ctx);
456        }
457        if let Some(child) = current.previous_child.take()
458            && !*ctx.limit_exceeded
459            && translation_unit
460            && macro_fractured_function(child)
461            && macro_fracture_boundary(child).is_some()
462        {
463            if current.fractured_function_scope {
464                ctx.bindings.exit_scope();
465                ctx.local_shadows.exit_scope();
466            }
467            ctx.bindings.enter_scope();
468            ctx.local_shadows.enter_scope();
469            seed_fractured_function_prefix(child, ctx);
470            current.fractured_function_scope = true;
471        }
472        // Unwind every entered scope once the hit limit has been reached.
473        if !*ctx.limit_exceeded
474            && let Some(child) = current.next_child.take()
475        {
476            current.next_child = child.next_named_sibling();
477            if translation_unit && current.fractured_function_scope && child.kind() == "ERROR" {
478                ctx.bindings.exit_scope();
479                ctx.local_shadows.exit_scope();
480                current.fractured_function_scope = false;
481            }
482            current.previous_child = Some(child);
483            stack.push(Frame::new(child));
484            continue;
485        }
486        if current.fractured_function_scope {
487            ctx.bindings.exit_scope();
488            ctx.local_shadows.exit_scope();
489        }
490        if current.enters_scope {
491            ctx.bindings.exit_scope();
492            ctx.local_shadows.exit_scope();
493        }
494        if current.enters_using_enum_scope {
495            ctx.using_enum_owners.exit_scope();
496        }
497        stack.pop();
498    }
499}
500
501fn macro_fractured_function(node: Node<'_>) -> bool {
502    if node.kind() != "function_definition" {
503        return false;
504    }
505    let Some(body) = node.child_by_field_name("body") else {
506        return false;
507    };
508    let mut stack = vec![body];
509    while let Some(current) = stack.pop() {
510        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
511            return true;
512        }
513        let mut cursor = current.walk();
514        stack.extend(current.named_children(&mut cursor));
515    }
516    false
517}
518
519/// Return the recovery boundary following a fractured macro-generated callable.
520pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
521    let first_orphan = node.next_named_sibling()?;
522    if !matches!(
523        first_orphan.kind(),
524        "expression_statement"
525            | "compound_statement"
526            | "preproc_if"
527            | "preproc_ifdef"
528            | "if_statement"
529            | "for_statement"
530            | "while_statement"
531            | "do_statement"
532            | "return_statement"
533    ) {
534        return None;
535    }
536    let mut sibling = Some(first_orphan);
537    while let Some(current) = sibling {
538        if current.kind() == "ERROR" {
539            return Some(current.end_byte());
540        }
541        if matches!(
542            current.kind(),
543            "function_definition" | "declaration" | "type_definition"
544        ) {
545            return None;
546        }
547        sibling = current.next_named_sibling();
548    }
549    None
550}
551
552fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
553    let cutoff = node.end_byte().saturating_sub(1);
554    seed_fractured_active_path(node, cutoff, ctx);
555}
556
557fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
558    let mut stack = vec![node];
559    while let Some(node) = stack.pop() {
560        if node.start_byte() >= cutoff {
561            continue;
562        }
563        let enters_scope = matches!(
564            node.kind(),
565            "compound_statement"
566                | "function_definition"
567                | "lambda_expression"
568                | "for_range_loop"
569                | "for_statement"
570                | "while_statement"
571                | "if_statement"
572                | "class_specifier"
573                | "struct_specifier"
574                | "union_specifier"
575        );
576        if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
577            continue;
578        }
579        seed_declarations(node, ctx);
580        push_named_children_reversed(node, &mut stack);
581    }
582}
583
584fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
585    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
586        || (ctx.spec.kind != TargetKind::Type
587            && indexed_recovered_class_field_declaration(node, ctx))
588    {
589        return;
590    }
591    match node.kind() {
592        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
593        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
594        "for_range_loop" => seed_range_binding(node, ctx),
595        "expression_statement" | "macro_type_specifier" => {
596            seed_function_macro_local_binding(node, ctx)
597        }
598        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
599        "using_declaration" => seed_using_enum(node, ctx),
600        _ => {}
601    }
602}
603
604fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
605    let Some(binding) = ctx
606        .visibility
607        .function_macro_local_binding(ctx.file, node, ctx.source)
608    else {
609        return;
610    };
611    if ctx.spec.kind == TargetKind::Type {
612        ctx.bindings.declare_shadow(binding.name);
613        return;
614    }
615    let normalized = normalize_cpp_type_name(&binding.type_name);
616    let unit = binding
617        .type_node
618        .and_then(|type_node| {
619            ctx.visibility
620                .resolve_type_node_result(ctx.file, type_node, ctx.source)
621                .ok()
622                .flatten()
623        })
624        .or_else(|| {
625            ctx.visibility
626                .canonical_type_for_reference(ctx.file, &normalized)
627        })
628        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
629    ctx.bindings.seed_symbol(
630        binding.name,
631        CppScanBinding::from_type_name(
632            normalized,
633            unit,
634            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
635        ),
636    );
637}
638
639fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
640    let Some(binding) =
641        ctx.visibility
642            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
643    else {
644        return;
645    };
646    let normalized = normalize_cpp_type_name(&binding.type_name);
647    let unit = binding.proven_unit.clone().or_else(|| {
648        binding
649            .type_node
650            .and_then(|type_node| {
651                ctx.visibility
652                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
653                    .ok()
654                    .flatten()
655            })
656            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
657    });
658    if let Some(unit) = unit {
659        ctx.bindings.seed_symbol(
660            binding.name,
661            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
662        );
663    }
664}
665
666fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
667    if node.kind() != "declaration"
668        || !has_function_scope_ancestor(node)
669        || has_ancestor_kind(node, "lambda_expression")
670        || !(has_recovered_class_shape_ancestor(node)
671            || has_malformed_wrapper_function_definition_ancestor(node))
672    {
673        return false;
674    }
675    let context = enclosing_context(node, ctx);
676    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
677        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
678}
679
680fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
681    if !ctx.needs_using_enum_member_resolution {
682        return;
683    }
684    if let Some((unit, UsingEnumDeclarationScope::Block)) =
685        resolved_using_enum_declaration(node, ctx)
686    {
687        ctx.using_enum_owners.import(unit);
688    }
689}
690
691fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
692    // Class and local-struct fields are resolved from their enclosing owner
693    // when they appear as an unqualified receiver. Seeding them into the
694    // function-wide binding scope would make same-spelled fields from sibling
695    // owners overwrite one another before the owner-aware path runs.
696    if node.kind() == "field_declaration" {
697        return;
698    }
699    let type_node = node
700        .child_by_field_name("type")
701        .or_else(|| first_type_child(node));
702    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
703    let mut cursor = node.walk();
704    for child in node.named_children(&mut cursor) {
705        let declarator = declaration_declarator(node, child);
706        let Some(declarator) = declarator else {
707            continue;
708        };
709        let Some(name) = extract_variable_name(declarator, ctx.source) else {
710            continue;
711        };
712        if declarator.kind() == "function_declarator"
713            && !constructor_style_local_declaration(
714                ctx.visibility,
715                ctx.file,
716                ctx.source,
717                declarator,
718                type_text.as_deref(),
719                &ctx.bindings,
720            )
721        {
722            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
723                ctx.local_shadows.declare_shadow(name);
724            }
725            continue;
726        }
727        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
728            ctx.local_shadows.declare_shadow(name.clone());
729        }
730        if ctx.spec.kind == TargetKind::Type {
731            ctx.bindings.declare_shadow(name);
732            continue;
733        }
734        let value = child.child_by_field_name("value");
735        seed_binding_from_type_or_value(&name, type_node, value, ctx);
736    }
737}
738
739fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
740    if !parameter_belongs_to_callable_scope(node) {
741        return;
742    }
743    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
744        return;
745    };
746    if has_function_scope_ancestor(node) {
747        ctx.local_shadows.declare_shadow(name.clone());
748    }
749    if ctx.spec.kind == TargetKind::Type {
750        ctx.bindings.declare_shadow(name);
751        return;
752    }
753    seed_binding_from_type_or_value(&name, type_node, None, ctx);
754}
755
756fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
757    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
758        return;
759    };
760    if has_function_scope_ancestor(node) {
761        ctx.local_shadows.declare_shadow(name.clone());
762    }
763    if ctx.spec.kind == TargetKind::Type {
764        ctx.bindings.declare_shadow(name);
765        return;
766    }
767    seed_binding_from_type_or_value(&name, type_node, None, ctx);
768}
769
770fn has_function_scope_ancestor(node: Node<'_>) -> bool {
771    let mut current = node.parent();
772    while let Some(parent) = current {
773        // Error recovery can wrap a complete namespace in a bogus outer
774        // `function_definition` (for example when an object-like namespace
775        // macro is parsed as a return type).  Declarations below that
776        // namespace are still namespace-scoped: do not let the malformed
777        // callable envelope seed them as local shadows.  A real function
778        // body is encountered before its enclosing namespace, so this keeps
779        // ordinary local binding detection unchanged.
780        if parent.kind() == "namespace_definition" {
781            return false;
782        }
783        if parent.kind() == "function_definition" {
784            // The malformed sentinel envelope is not a callable scope. Its
785            // body may contain a recovered namespace (or, in a smaller error
786            // tree, only an ERROR node standing in for that namespace), so
787            // declarations directly below it must remain namespace-scoped.
788            // Real nested functions are encountered first and still seed
789            // ordinary local bindings.
790            return !is_malformed_wrapper_function_definition(parent);
791        }
792        if parent.kind() == "lambda_expression" {
793            return true;
794        }
795        current = parent.parent();
796    }
797    false
798}
799
800fn seed_binding_from_type_or_value(
801    name: &str,
802    type_node: Option<Node<'_>>,
803    value: Option<Node<'_>>,
804    ctx: &mut ScanCtx<'_>,
805) {
806    if name.is_empty() {
807        return;
808    }
809    let resolved = type_node
810        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
811        .map(|node| {
812            let text = node_text(node, ctx.source);
813            let name = normalize_cpp_type_name(text);
814            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
815                return CppScanBinding::from_type_name(
816                    name,
817                    Some(unit),
818                    cpp_type_text_pointer_depth(text),
819                );
820            }
821            // Bare type names need lexical ownership before the coarse visible-name
822            // fallback (two namespaces can each declare `CopyResult`). Template
823            // references keep the specialization-aware resolver first because a
824            // component-only lexical lookup cannot rank partial specializations.
825            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
826                if cpp_template_reference_arguments(node, ctx.source).is_some() {
827                    return None;
828                }
829                match enclosing_lexical_scope_components(
830                    node,
831                    &ctx.analyzer,
832                    ctx.visibility,
833                    ctx.file,
834                    ctx.source,
835                ) {
836                    LexicalScopeResolution::Resolved(scope) => Some(scope),
837                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
838                }
839            });
840            let unit = lexical_scope
841                .as_deref()
842                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
843                .or_else(|| {
844                    match ctx
845                        .visibility
846                        .resolve_type_node_result(ctx.file, node, ctx.source)
847                    {
848                        Ok(Some(unit)) => Some(unit),
849                        Ok(None) => ctx
850                            .visibility
851                            .canonical_type_for_reference(ctx.file, &name)
852                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
853                        Err(_) => None,
854                    }
855                });
856            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
857        })
858        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
859
860    if let Some(resolved) = resolved {
861        ctx.bindings.seed_symbol(name.to_string(), resolved);
862    } else if let Some(value) = value
863        && value.kind() == "identifier"
864    {
865        ctx.bindings
866            .alias_symbol(name.to_string(), node_text(value, ctx.source));
867    } else {
868        ctx.bindings.declare_shadow(name.to_string());
869    }
870}
871
872fn resolve_seed_type_node_lexically(
873    node: Node<'_>,
874    ctx: &ScanCtx<'_>,
875    scope: &[String],
876) -> Option<CodeUnit> {
877    let (components, global) = type_reference_components(node, ctx.source)?;
878    let resolution = match scan_owner_type(ctx) {
879        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
880            &ctx.analyzer,
881            ctx.file,
882            &components,
883            global,
884            scope,
885            target,
886        ),
887        None => ctx.visibility.resolve_type_components_lexically(
888            &ctx.analyzer,
889            ctx.file,
890            &components,
891            global,
892            scope,
893        ),
894    };
895    match resolution {
896        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
897        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
898    }
899}
900
901/// The type this scan is asking about: the owner of a member target, or the
902/// target itself when it is a type.
903///
904/// C headers routinely declare one typedef name once per branch of an
905/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
906/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
907/// are alternate spellings of one name rather than competing declarations, and
908/// canonicalizing without the scan's own type identity picks between them by
909/// declaration order. That ordering can bind a receiver to the branch that does
910/// not declare the queried member, which drops every reference in the branch
911/// that does. Naming the identity keeps the target-preserving rule the forward
912/// resolver already applies (`unique_type_candidate_preserving_target`).
913fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
914    // Target-preserving lookup can reconcile logical declarations across files.
915    // A receiver must not borrow an owner from a header its caller cannot see.
916    ctx.spec
917        .owner
918        .as_ref()
919        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
920}
921
922const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
923
924fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
925    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
926}
927
928fn infer_type_from_value_with_budget(
929    node: Node<'_>,
930    ctx: &ScanCtx<'_>,
931    remaining_call_depth: usize,
932) -> Option<CppScanBinding> {
933    match node.kind() {
934        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
935            infer_cpp_initializer_binding(
936                &ctx.analyzer,
937                ctx.visibility,
938                ctx.file,
939                ctx.source,
940                node,
941                None,
942            )
943        }
944        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
945            &ctx.analyzer,
946            ctx.visibility,
947            ctx.file,
948            ctx.source,
949            node,
950            Some(&|receiver, source| {
951                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
952            }),
953        ),
954        "initializer_list" => None,
955        "identifier" => {
956            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
957            resolved
958                .as_precise()?
959                .iter()
960                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
961                .cloned()
962        }
963        _ => {
964            let text = node_text(node, ctx.source);
965            let name = normalize_cpp_type_name(text);
966            ctx.visibility
967                .resolve_type(ctx.file, &name)
968                .map(|unit| CppScanBinding::from_unit(unit, 0))
969        }
970    }
971}
972
973/// Whether a member named `identifier` can be spelled anywhere in this file's
974/// syntax.
975///
976/// Both member scans below -- [`maybe_record_method_hit`] and
977/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
978/// of a syntax node against the target's member name, so a file whose syntax
979/// never spells that name yields neither a proven nor an unproven hit. That
980/// makes this an exact admission test rather than an approximation: candidate
981/// discovery can drop such a file without narrowing what the query proves.
982///
983/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
984/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
985/// replacement, which
986/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
987/// sentinel parse, and an `ERROR` region -- so those admit on containment
988/// instead of on a token match. A `comment` is the one subtree no scan reads,
989/// so it is skipped.
990///
991/// Defined only for a plain identifier: [`name_matches_callable`] widens the
992/// comparison for an `operator` name to the `operator` token alone, and a
993/// destructor or conversion-function name is not spelled the way it is
994/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
995/// that condition.
996pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
997    debug_assert!(
998        cpp_member_is_spelled_at_references(identifier),
999        "member admission is defined only for a plain identifier: {identifier:?}"
1000    );
1001    let mut spelled = false;
1002    walk_named_tree_preorder(root, true, |node| match node.kind() {
1003        "comment" => WalkControl::SkipChildren,
1004        "identifier"
1005        | "field_identifier"
1006        | "type_identifier"
1007        | "namespace_identifier"
1008        | "statement_identifier" => {
1009            if node_text(node, source).trim() == identifier {
1010                spelled = true;
1011                WalkControl::Break
1012            } else {
1013                WalkControl::SkipChildren
1014            }
1015        }
1016        "preproc_arg" | "ERROR" => {
1017            if node_text(node, source).contains(identifier) {
1018                spelled = true;
1019                WalkControl::Break
1020            } else {
1021                // The node's own text covers every descendant's bytes, so a
1022                // miss here is a miss for the whole subtree.
1023                WalkControl::SkipChildren
1024            }
1025        }
1026        _ => WalkControl::Continue,
1027    });
1028    spelled
1029}
1030
1031/// Whether every reference to a member named `identifier` must spell it.
1032///
1033/// An `operator` name is matched by [`name_matches_callable`] through the bare
1034/// `operator` token, and a destructor or conversion function is recorded under
1035/// a name its call sites do not write, so neither admits a spelling test.
1036pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1037    let mut characters = identifier.chars();
1038    characters
1039        .next()
1040        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1041        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1042        && !identifier.starts_with("operator")
1043}
1044
1045fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1046    match ctx.spec.kind {
1047        TargetKind::Type => maybe_record_type_hit(node, ctx),
1048        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1049        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1050        TargetKind::Method => maybe_record_method_hit(node, ctx),
1051        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1052        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1053        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1054    }
1055}
1056
1057fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1058    if node_text(node, ctx.source) != ctx.spec.member_name {
1059        return;
1060    }
1061    if is_ordinary_macro_reference_node(node) {
1062        if ctx.visibility.macro_binding_matches_target_declaration_at(
1063            ctx.file,
1064            &ctx.spec.member_name,
1065            node.start_byte(),
1066            ctx.spec.target.source(),
1067            &ctx.target_macro_declaration_bytes,
1068        ) {
1069            *ctx.raw_match_count += 1;
1070            push_hit(node, ctx);
1071            return;
1072        }
1073        match ctx.visibility.resolve_ordinary_macro_reference(
1074            &ctx.analyzer,
1075            ctx.file,
1076            node,
1077            ctx.source,
1078        ) {
1079            OrdinaryMacroReferenceResolution::Resolved(unit)
1080                if ctx.target_group.contains(&unit) =>
1081            {
1082                *ctx.raw_match_count += 1;
1083                push_hit(node, ctx);
1084            }
1085            OrdinaryMacroReferenceResolution::Ambiguous
1086                if ctx
1087                    .visibility
1088                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1089            {
1090                *ctx.raw_match_count += 1;
1091                push_unproven_hit(node, ctx);
1092            }
1093            OrdinaryMacroReferenceResolution::Resolved(_)
1094            | OrdinaryMacroReferenceResolution::Ambiguous
1095            | OrdinaryMacroReferenceResolution::Missing => {}
1096        }
1097        return;
1098    }
1099    if !matches!(
1100        node.kind(),
1101        "identifier"
1102            | "field_identifier"
1103            | "type_identifier"
1104            | "namespace_identifier"
1105            | "preproc_arg"
1106    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1107        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1108            && parent
1109                .child_by_field_name("name")
1110                .is_some_and(|name| same_node(name, node))
1111    }) {
1112        return;
1113    }
1114    if ctx.visibility.macro_binding_matches_target_declaration_at(
1115        ctx.file,
1116        &ctx.spec.member_name,
1117        node.start_byte(),
1118        ctx.spec.target.source(),
1119        &ctx.target_macro_declaration_bytes,
1120    ) {
1121        *ctx.raw_match_count += 1;
1122        push_hit(node, ctx);
1123    } else if ctx.visibility.macro_name_may_be_bound_at(
1124        ctx.file,
1125        &ctx.spec.member_name,
1126        node.start_byte(),
1127    ) && ctx
1128        .visibility
1129        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1130    {
1131        *ctx.raw_match_count += 1;
1132        push_unproven_hit(node, ctx);
1133    }
1134}
1135
1136fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1137    let kind = node.kind();
1138    if ctx.uses_c_semantics
1139        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1140            .is_some()
1141    {
1142        return;
1143    }
1144    if kind == "preproc_arg" {
1145        maybe_record_object_macro_replacement_type_hits(node, ctx);
1146        return;
1147    }
1148    if let Some(type_node) = ctx
1149        .visibility
1150        .function_macro_type_argument(ctx.file, node, ctx.source)
1151    {
1152        if ctx
1153            .local_shadows
1154            .is_shadowed(node_text(type_node, ctx.source))
1155        {
1156            return;
1157        }
1158        if let LexicalTypeResolution::Resolved {
1159            unit, candidates, ..
1160        } = resolve_type_node_lexically_for_target(
1161            type_node,
1162            &ctx.analyzer,
1163            ctx.visibility,
1164            &ctx.ordinary_type_imports,
1165            ctx.file,
1166            ctx.source,
1167            &ctx.spec.target,
1168            Some(&ctx.lexical_scope_cache),
1169            ctx.recovered_sentinel_scope(type_node).as_deref(),
1170        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1171        {
1172            *ctx.raw_match_count += 1;
1173            push_type_hit(type_node, ctx);
1174        }
1175        return;
1176    }
1177    let recovered_exported_class_base =
1178        matches!(
1179            kind,
1180            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1181        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1182    if kind == "field_declaration"
1183        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1184    {
1185        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1186        return;
1187    }
1188    if kind == "qualified_identifier"
1189        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1190    {
1191        // A member-pointer owner can itself end in a nested alias, as in
1192        // `type_identity<T>::type::*`. Resolving the complete owner
1193        // canonicalizes that alias to its underlying type and loses the alias
1194        // declaration that inverse lookup is targeting. Retain every
1195        // structurally proven qualifier component before asking for the
1196        // canonical owner type.
1197        if ctx
1198            .analyzer
1199            .type_alias_provider()
1200            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1201            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1202            && let Some(terminal) = owner.nodes.last().copied()
1203        {
1204            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1205                return;
1206            }
1207            if ctx.visibility.external_type_candidate_visible_in_context(
1208                &ctx.analyzer,
1209                ctx.file,
1210                &ctx.spec.target,
1211                terminal,
1212            ) || ctx
1213                .visibility
1214                .dependent_member_pointer_alias_visible_in_context(
1215                    &ctx.analyzer,
1216                    ctx.file,
1217                    &ctx.spec.target,
1218                    &owner.names,
1219                    terminal,
1220                )
1221            {
1222                *ctx.raw_match_count += 1;
1223                push_type_hit(terminal, ctx);
1224            } else if ctx
1225                .visibility
1226                .is_physically_visible(ctx.file, &ctx.spec.target)
1227                && ctx.visibility.declaration_guards_compatible_at_reference(
1228                    &ctx.analyzer,
1229                    ctx.file,
1230                    &ctx.spec.target,
1231                    terminal,
1232                )
1233            {
1234                // The structured owner and terminal identify a candidate, but
1235                // do not establish its conditional availability.
1236                *ctx.raw_match_count += 1;
1237                push_unproven_hit(terminal, ctx);
1238            }
1239            return;
1240        }
1241        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1242            *ctx.raw_match_count += 1;
1243            for scope in scopes {
1244                push_type_hit(scope, ctx);
1245            }
1246            return;
1247        }
1248        return;
1249    }
1250    if kind == "pointer_expression"
1251        && let Some(value) = qualified_callable_value(node)
1252        && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1253    {
1254        *ctx.raw_match_count += 1;
1255        match owner_hit {
1256            TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1257            TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1258        }
1259        return;
1260    }
1261    if kind == "call_expression" {
1262        maybe_record_direct_temporary_type_hit(node, ctx);
1263        return;
1264    }
1265    if !matches!(
1266        kind,
1267        "identifier"
1268            | "namespace_identifier"
1269            | "qualified_identifier"
1270            | "scoped_identifier"
1271            | "scoped_type_identifier"
1272            | "template_function"
1273            | "template_type"
1274            | "type_descriptor"
1275            | "type_identifier"
1276            | "using_declaration"
1277    ) {
1278        return;
1279    }
1280    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1281    let template_function_type = cpp_template_function_has_type_owner(node);
1282    let recovered_qualified_friend =
1283        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1284    if !recovered_type
1285        && !recovered_qualified_friend
1286        && !recovered_exported_class_base
1287        && matches!(
1288            kind,
1289            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1290        )
1291        && !type_reference_components_may_name_target(node, ctx)
1292    {
1293        return;
1294    }
1295    if ctx.ancestry.parent(node).is_some_and(|parent| {
1296        parent.kind() == "operator_cast"
1297            && parent
1298                .child_by_field_name("type")
1299                .is_some_and(|target| same_node(target, node))
1300    }) {
1301        return;
1302    }
1303    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1304        *ctx.raw_match_count += 1;
1305        push_type_hit(hit, ctx);
1306        return;
1307    }
1308    if matches!(kind, "identifier" | "template_function")
1309        && parent_if_field(node, "call_expression", "function").is_some()
1310    {
1311        return;
1312    }
1313    if kind == "using_declaration" {
1314        let (resolution, type_node) =
1315            if let Some(type_node) = using_enum_declaration_type_node(node) {
1316                (
1317                    resolve_using_enum_declaration_owner(
1318                        node,
1319                        &ctx.analyzer,
1320                        ctx.visibility,
1321                        &ctx.ordinary_type_imports,
1322                        ctx.file,
1323                        ctx.source,
1324                    ),
1325                    type_node,
1326                )
1327            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1328                (
1329                    resolve_ordinary_using_declaration_owner(
1330                        node,
1331                        &ctx.analyzer,
1332                        ctx.visibility,
1333                        ctx.file,
1334                        ctx.source,
1335                    ),
1336                    type_node,
1337                )
1338            } else {
1339                return;
1340            };
1341        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1342            && same_visible_symbol(&unit, &ctx.spec.target)
1343        {
1344            *ctx.raw_match_count += 1;
1345            push_type_hit(type_node, ctx);
1346        }
1347        return;
1348    }
1349    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1350        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1351            || local_type_name_shadows(node, ctx)
1352        {
1353            return;
1354        }
1355        if let LexicalTypeResolution::Resolved {
1356            unit, candidates, ..
1357        } = resolve_type_node_lexically_for_target(
1358            node,
1359            &ctx.analyzer,
1360            ctx.visibility,
1361            &ctx.ordinary_type_imports,
1362            ctx.file,
1363            ctx.source,
1364            &ctx.spec.target,
1365            Some(&ctx.lexical_scope_cache),
1366            ctx.recovered_sentinel_scope(node).as_deref(),
1367        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1368        {
1369            *ctx.raw_match_count += 1;
1370            push_type_hit(node, ctx);
1371        }
1372        return;
1373    }
1374    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1375        // A missing `::` can put either the macro or the real type in the
1376        // recovered scope. Resolve both candidates against this inverse
1377        // target before choosing one: a unique match is routed through the
1378        // ordinary target-guided path, while two distinct matches are
1379        // ambiguous and must not invent a hit. With no target match, retain
1380        // the existing recovered-scope path below for its conservative
1381        // fallback behaviour.
1382        let mut matching = Vec::new();
1383        for candidate in [node, type_node] {
1384            if matching
1385                .iter()
1386                .any(|existing| same_node(*existing, candidate))
1387            {
1388                continue;
1389            }
1390            if let LexicalTypeResolution::Resolved {
1391                unit, candidates, ..
1392            } = resolve_type_node_lexically_for_target(
1393                candidate,
1394                &ctx.analyzer,
1395                ctx.visibility,
1396                &ctx.ordinary_type_imports,
1397                ctx.file,
1398                ctx.source,
1399                &ctx.spec.target,
1400                Some(&ctx.lexical_scope_cache),
1401                ctx.recovered_sentinel_scope(candidate).as_deref(),
1402            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1403            {
1404                matching.push(candidate);
1405            }
1406        }
1407        match matching.as_slice() {
1408            [candidate] if !same_node(*candidate, node) => {
1409                maybe_record_type_hit(*candidate, ctx);
1410                return;
1411            }
1412            [candidate] if same_node(*candidate, node) => {}
1413            [] => {}
1414            _ => return,
1415        }
1416    }
1417    if !recovered_type
1418        && !template_function_type
1419        && !matches!(
1420            kind,
1421            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1422        )
1423    {
1424        return;
1425    }
1426    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1427        components.len() == 1
1428            && !global
1429            && (local_type_name_shadows(node, ctx)
1430                || ctx
1431                    .analyzer
1432                    .type_alias_provider()
1433                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1434                    && nearer_type_name_shadows_structured_reference(node, ctx))
1435    }) {
1436        return;
1437    }
1438    if !recovered_type
1439        && !recovered_qualified_friend
1440        && !recovered_exported_class_base
1441        && ctx.ancestry.parent(node).is_some_and(|parent| {
1442            parent.kind() == "alias_declaration"
1443                && parent
1444                    .child_by_field_name("name")
1445                    .is_some_and(|name| same_node(name, node))
1446        })
1447    {
1448        return;
1449    }
1450    #[cfg(any(test, feature = "test-support"))]
1451    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1452    if !recovered_type
1453        && !recovered_qualified_friend
1454        && !recovered_exported_class_base
1455        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1456        && is_declaration_name(node)
1457        && let Some(owners) = out_of_line_member_definition_owner(
1458            &ctx.analyzer,
1459            ctx.visibility,
1460            ctx.file,
1461            ctx.source,
1462            node,
1463        )
1464    {
1465        *ctx.raw_match_count += 1;
1466        let mut matched_owner = false;
1467        for (owner_node, owner) in owners.owners {
1468            if same_visible_symbol(&owner, &ctx.spec.target) {
1469                matched_owner = true;
1470                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1471            }
1472        }
1473        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1474            for scope in scopes {
1475                push_hit(scope, ctx);
1476            }
1477        } else if !matched_owner
1478            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1479        {
1480            push_unproven_hit(scope, ctx);
1481        }
1482        return;
1483    }
1484    if !recovered_type
1485        && !recovered_qualified_friend
1486        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1487        && is_declaration_name(node)
1488        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1489    {
1490        *ctx.raw_match_count += 1;
1491        push_type_hit(owner, ctx);
1492        return;
1493    }
1494    // A qualified template-id is represented as a qualified_identifier whose
1495    // name child is the template_type. Resolve the complete qualified
1496    // reference from that inner template node below; handling the outer node
1497    // independently would either lose the qualifier or emit a duplicate,
1498    // wider hit range.
1499    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1500        && matches!(
1501            node.kind(),
1502            "qualified_identifier" | "scoped_type_identifier"
1503        )
1504        && node
1505            .child_by_field_name("name")
1506            .is_some_and(|name| name.kind() == "template_type")
1507    {
1508        return;
1509    }
1510    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1511        *ctx.raw_match_count += 1;
1512        push_unproven_hit(scope, ctx);
1513        return;
1514    }
1515    if !recovered_type
1516        && !is_nested_type_node(node)
1517        && matches!(
1518            node.kind(),
1519            "qualified_identifier" | "scoped_type_identifier"
1520        )
1521        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1522    {
1523        *ctx.raw_match_count += 1;
1524        for scope in scopes {
1525            push_type_hit(scope, ctx);
1526        }
1527        return;
1528    }
1529    if !recovered_type && is_nested_type_node(node) {
1530        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1531            *ctx.raw_match_count += 1;
1532            push_type_hit(hit, ctx);
1533            return;
1534        }
1535        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1536            *ctx.raw_match_count += 1;
1537            push_type_hit(hit, ctx);
1538            return;
1539        }
1540        // A concrete template specialization can be absent from the coarse
1541        // per-file component-name index: that index contains the primary name
1542        // while this recovered child may expose only the malformed template
1543        // leaf.  Resolve the complete enclosing template before applying the
1544        // component prefilter so an exact target candidate can prove this
1545        // reference.  Non-specialization targets retain the cheap gate.
1546        let nested_template = if node.kind() == "template_type" {
1547            Some(node)
1548        } else {
1549            ctx.ancestry.parent(node).filter(|parent| {
1550                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1551            })
1552        };
1553        // Concrete specializations need their nested template-id inspected.
1554        // An ordinary alias application does too when it is itself the scope
1555        // of a member-qualified type, because the enclosing node denotes the
1556        // member rather than the alias. In every other primary/alias shape the
1557        // enclosing structured type owns the hit range; descending would emit
1558        // a duplicate terminal subrange.
1559        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1560            let enclosing_qualified_type_owns_range =
1561                ctx.ancestry.parent(template).is_some_and(|parent| {
1562                    matches!(
1563                        parent.kind(),
1564                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1565                    ) && parent.child_by_field_name("name") == Some(template)
1566                });
1567            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1568                return false;
1569            };
1570            let Some(name) = template_reference_name_node(template) else {
1571                return false;
1572            };
1573            let alias_candidates = ctx
1574                .visibility
1575                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1576                .filter(|candidate| alias_provider.is_type_alias(candidate))
1577                .cloned()
1578                .collect::<Vec<_>>();
1579            let direct_target_alias_visible = !alias_candidates
1580                .iter()
1581                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1582                || ctx.visibility.external_type_candidate_visible_in_context(
1583                    &ctx.analyzer,
1584                    ctx.file,
1585                    &ctx.spec.target,
1586                    template,
1587                );
1588            !enclosing_qualified_type_owns_range
1589                && direct_target_alias_visible
1590                && template_reference_candidates_select_target(
1591                    template,
1592                    &alias_candidates,
1593                    &ctx.analyzer,
1594                    ctx.visibility,
1595                    ctx.file,
1596                    ctx.source,
1597                    &ctx.spec.target,
1598                )
1599        });
1600        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1601            return;
1602        }
1603        if nested_alias_qualifier {
1604            let template = nested_template.expect("a nested alias qualifier has a template node");
1605            *ctx.raw_match_count += 1;
1606            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1607                .unwrap_or_else(|| type_reference_hit_node(template));
1608            push_type_hit(hit, ctx);
1609            return;
1610        }
1611        // For concrete specializations, the visible identifier index can
1612        // already contain the exact template spelling even when lexical
1613        // resolution rejects the recovered child (the child itself has no
1614        // argument list). Select the name as structured evidence;
1615        // `push_type_hit` expands it to the complete written template-id.
1616        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1617            && let Some(template) = nested_template
1618            && template_type_component_preserves_target(
1619                template,
1620                &ctx.visibility
1621                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1622                    .cloned()
1623                    .collect::<Vec<_>>(),
1624                ctx,
1625            )
1626        {
1627            *ctx.raw_match_count += 1;
1628            let hit = template
1629                .child_by_field_name("name")
1630                .filter(|name| name.kind() == "type_identifier")
1631                .unwrap_or(template);
1632            push_type_hit(hit, ctx);
1633            return;
1634        }
1635        if let Some(template) = nested_template
1636            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1637        {
1638            *ctx.raw_match_count += 1;
1639            let hit =
1640                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1641                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1642                        template
1643                            .child_by_field_name("name")
1644                            .filter(|name| name.kind() == "type_identifier")
1645                            .unwrap_or(template)
1646                    } else {
1647                        type_reference_hit_node(template)
1648                    }
1649                });
1650            push_type_hit(hit, ctx);
1651        }
1652        return;
1653    }
1654    if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1655        let direct_target = resolve_qualified_call_target(
1656            call,
1657            node,
1658            &ctx.analyzer,
1659            ctx.visibility,
1660            &ctx.ordinary_type_imports,
1661            ctx.file,
1662            ctx.source,
1663        );
1664        if matches!(direct_target, BareCallTargetResolution::Type(_))
1665            && let LexicalTypeResolution::Resolved {
1666                unit, candidates, ..
1667            } = resolve_type_node_lexically_for_target(
1668                node,
1669                &ctx.analyzer,
1670                ctx.visibility,
1671                &ctx.ordinary_type_imports,
1672                ctx.file,
1673                ctx.source,
1674                &ctx.spec.target,
1675                Some(&ctx.lexical_scope_cache),
1676                ctx.recovered_sentinel_scope(node).as_deref(),
1677            )
1678            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1679        {
1680            *ctx.raw_match_count += 1;
1681            push_resolved_type_hit(node, &candidates, ctx);
1682        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1683            *ctx.raw_match_count += 1;
1684            for scope in scopes {
1685                push_type_hit(scope, ctx);
1686            }
1687        } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1688            *ctx.raw_match_count += 1;
1689            match owner_hit {
1690                TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1691                TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1692                    push_unproven_hit(scope, ctx)
1693                }
1694            }
1695        }
1696        return;
1697    }
1698    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1699        *ctx.raw_match_count += 1;
1700        if proven {
1701            push_type_hit(hit, ctx);
1702        } else {
1703            push_unproven_hit(hit, ctx);
1704        }
1705        return;
1706    }
1707    if !recovered_type
1708        && !recovered_qualified_friend
1709        && !recovered_exported_class_base
1710        && is_declaration_name(node)
1711    {
1712        let mut matched_owner = false;
1713        if let Some(owners) = out_of_line_member_definition_owner(
1714            &ctx.analyzer,
1715            ctx.visibility,
1716            ctx.file,
1717            ctx.source,
1718            node,
1719        ) {
1720            for (owner_node, owner) in owners.owners {
1721                if same_visible_symbol(&owner, &ctx.spec.target) {
1722                    matched_owner = true;
1723                    *ctx.raw_match_count += 1;
1724                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1725                }
1726            }
1727        }
1728        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1729            *ctx.raw_match_count += 1;
1730            for scope in scopes {
1731                push_hit(scope, ctx);
1732            }
1733        } else if !matched_owner
1734            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1735        {
1736            *ctx.raw_match_count += 1;
1737            push_unproven_hit(scope, ctx);
1738        }
1739        return;
1740    }
1741    let hit_node = node;
1742    let text = node_text(hit_node, ctx.source);
1743    let type_resolution = if hit_node.kind() == "template_type"
1744        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1745    {
1746        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1747            resolve_type_node_lexically_for_target(
1748                hit_node,
1749                &ctx.analyzer,
1750                ctx.visibility,
1751                &ctx.ordinary_type_imports,
1752                ctx.file,
1753                ctx.source,
1754                &ctx.spec.target,
1755                Some(&ctx.lexical_scope_cache),
1756                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1757            )
1758        })
1759    } else {
1760        resolve_type_node_lexically_for_target(
1761            hit_node,
1762            &ctx.analyzer,
1763            ctx.visibility,
1764            &ctx.ordinary_type_imports,
1765            ctx.file,
1766            ctx.source,
1767            &ctx.spec.target,
1768            Some(&ctx.lexical_scope_cache),
1769            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1770        )
1771    };
1772    match type_resolution {
1773        LexicalTypeResolution::Resolved {
1774            unit, candidates, ..
1775        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1776            *ctx.raw_match_count += 1;
1777            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1778                for scope in scopes {
1779                    push_type_hit(scope, ctx);
1780                }
1781            } else {
1782                push_resolved_type_hit(hit_node, &candidates, ctx);
1783            }
1784            return;
1785        }
1786        LexicalTypeResolution::Resolved {
1787            unit: _,
1788            candidates,
1789            ..
1790        } => {
1791            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1792                *ctx.raw_match_count += 1;
1793                for scope in scopes {
1794                    push_type_hit(scope, ctx);
1795                }
1796            } else if let Some(hit) =
1797                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1798            {
1799                *ctx.raw_match_count += 1;
1800                push_unproven_hit(hit, ctx);
1801            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1802                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1803            {
1804                *ctx.raw_match_count += 1;
1805                push_type_hit(leaf, ctx);
1806            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1807                *ctx.raw_match_count += 1;
1808                push_unproven_hit(leaf, ctx);
1809            }
1810            return;
1811        }
1812        LexicalTypeResolution::Ambiguous => {
1813            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1814                *ctx.raw_match_count += 1;
1815                for scope in scopes {
1816                    push_type_hit(scope, ctx);
1817                }
1818            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1819                *ctx.raw_match_count += 1;
1820                push_unproven_hit(leaf, ctx);
1821            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1822                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1823                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1824            {
1825                *ctx.raw_match_count += 1;
1826                push_type_hit(leaf, ctx);
1827            }
1828            return;
1829        }
1830        LexicalTypeResolution::Missing => {
1831            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1832                &ctx.analyzer,
1833                ctx.file,
1834                hit_node,
1835                ctx.source,
1836            ) && same_visible_symbol(&unit, &ctx.spec.target)
1837            {
1838                *ctx.raw_match_count += 1;
1839                push_type_hit(hit_node, ctx);
1840                return;
1841            }
1842            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1843                *ctx.raw_match_count += 1;
1844                push_unproven_hit(leaf, ctx);
1845                return;
1846            }
1847            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1848                *ctx.raw_match_count += 1;
1849                push_unproven_hit(leaf, ctx);
1850                return;
1851            }
1852            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1853                *ctx.raw_match_count += 1;
1854                push_type_hit(leaf, ctx);
1855                return;
1856            }
1857            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1858                hit_node,
1859                &ctx.analyzer,
1860                ctx.visibility,
1861                ctx.file,
1862                ctx.source,
1863                &ctx.spec.target,
1864            );
1865            let raw_matches = matches!(
1866                raw_resolution,
1867                LexicalTypeResolution::Resolved {
1868                    ref unit,
1869                    ref candidates,
1870                    ..
1871                } if type_resolution_identifies_unit_target(
1872                    hit_node,
1873                    unit,
1874                    candidates,
1875                    &ctx.spec.target,
1876                    ctx,
1877                )
1878            );
1879            if raw_matches
1880                || type_node_has_exact_target_identity_without_visibility(
1881                    hit_node,
1882                    &ctx.analyzer,
1883                    ctx.visibility,
1884                    ctx.file,
1885                    ctx.source,
1886                    &ctx.spec.target,
1887                )
1888            {
1889                *ctx.raw_match_count += 1;
1890                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1891                return;
1892            }
1893        }
1894    }
1895    // A class-owned alias can be qualified by an owner made visible through a
1896    // namespace import, including an import in an earlier reopening of the
1897    // same unnamed namespace. Resolve that owner before requiring an
1898    // enclosing class for inherited nested-type lookup.
1899    if ctx
1900        .visibility
1901        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1902    {
1903        *ctx.raw_match_count += 1;
1904        push_type_hit(type_reference_hit_node(hit_node), ctx);
1905        return;
1906    }
1907    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1908        *ctx.raw_match_count += 1;
1909        for scope in scopes {
1910            push_type_hit(scope, ctx);
1911        }
1912        return;
1913    }
1914    if !name_mentions(text, &ctx.spec.member_name) {
1915        return;
1916    }
1917    *ctx.raw_match_count += 1;
1918    if !ctx.visibility.external_type_candidate_visible_in_context(
1919        &ctx.analyzer,
1920        ctx.file,
1921        &ctx.spec.target,
1922        hit_node,
1923    ) {
1924        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1925        if type_reference_resolves_away_from_target(unproven, ctx) {
1926            return;
1927        }
1928        push_unproven_hit(unproven, ctx);
1929    }
1930}
1931
1932fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1933    for reference in object_macro_replacement_type_references(node, ctx.source) {
1934        for component_count in 1..=reference.components.len() {
1935            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1936                node,
1937                &reference.components[..component_count],
1938                reference.global,
1939                &ctx.analyzer,
1940                ctx.visibility,
1941                &ctx.ordinary_type_imports,
1942                ctx.file,
1943                ctx.source,
1944                &ctx.spec.target,
1945                false,
1946                Some(&ctx.lexical_scope_cache),
1947            );
1948            let matches_target = match resolution {
1949                LexicalTypeResolution::Resolved {
1950                    unit, candidates, ..
1951                } => {
1952                    same_visible_symbol(&unit, &ctx.spec.target)
1953                        || candidates
1954                            .iter()
1955                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1956                }
1957                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1958                    &ctx.analyzer,
1959                    ctx.visibility,
1960                    ctx.file,
1961                    &reference.components[..component_count],
1962                )
1963                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1964                LexicalTypeResolution::Ambiguous => false,
1965            };
1966            if !matches_target {
1967                continue;
1968            }
1969            let range = &reference.component_ranges[component_count - 1];
1970            *ctx.raw_match_count += 1;
1971            push_type_hit_range(node, range.start, range.end, ctx);
1972        }
1973    }
1974}
1975
1976fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1977    node: Node<'tree>,
1978    ctx: &ScanCtx<'_>,
1979) -> Option<Node<'tree>> {
1980    let (components, global) = type_reference_components(node, ctx.source)?;
1981    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1982        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1983            node,
1984            &ctx.analyzer,
1985            ctx.visibility,
1986            ctx.file,
1987            ctx.source,
1988            false,
1989            false,
1990            Some(&ctx.lexical_scope_cache),
1991        ) {
1992            LexicalScopeResolution::Resolved(scope) => Some(scope),
1993            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1994        }
1995    })?;
1996    let OrdinaryTypeImportResolution::Resolved { target, .. } =
1997        compatible_foreign_type_import_resolution(
1998            node,
1999            &components,
2000            global,
2001            &ctx.analyzer,
2002            ctx.visibility,
2003            &ctx.ordinary_type_imports,
2004            ctx.file,
2005            ctx.source,
2006            &lexical_scope,
2007            Some(&ctx.spec.target),
2008        )
2009    else {
2010        return None;
2011    };
2012    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2013}
2014
2015/// Resolve `Owner::Nested` when complete type lookup cannot express the owner
2016/// as its first qualified component. This occurs when a using-directive
2017/// imports `Owner` and when inherited-type lookup injects it. Resolve the
2018/// owner prefix independently, then return the terminal node after that owner
2019/// proves the target's direct nested declaration; hit insertion expands it to
2020/// the complete written type.
2021fn target_guided_nested_type_terminal_hit<'tree>(
2022    node: Node<'tree>,
2023    ctx: &ScanCtx<'_>,
2024) -> Option<Node<'tree>> {
2025    // Returns a node of the caller's tree lifetime, which the context-scoped
2026    // ancestry index cannot name; this is one question per call.
2027    let qualified = node.parent().filter(|parent| {
2028        matches!(
2029            parent.kind(),
2030            "qualified_identifier" | "scoped_type_identifier"
2031        ) && parent.child_by_field_name("name") == Some(node)
2032    })?;
2033    let mut complete = qualified;
2034    while let Some(parent) = complete.parent().filter(|parent| {
2035        matches!(
2036            parent.kind(),
2037            "qualified_identifier" | "scoped_type_identifier"
2038        )
2039    }) {
2040        complete = parent;
2041    }
2042    let owner = qualified_owner_components(complete, ctx.source)?;
2043
2044    if let LexicalTypeResolution::Resolved {
2045        unit, candidates, ..
2046    } = resolve_type_node_lexically_for_target(
2047        complete,
2048        &ctx.analyzer,
2049        ctx.visibility,
2050        &ctx.ordinary_type_imports,
2051        ctx.file,
2052        ctx.source,
2053        &ctx.spec.target,
2054        Some(&ctx.lexical_scope_cache),
2055        ctx.recovered_sentinel_scope(complete).as_deref(),
2056    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2057        && ctx
2058            .analyzer
2059            .parent_of(&ctx.spec.target)
2060            .is_some_and(|parent| parent.is_class())
2061        && !ctx
2062            .analyzer
2063            .type_alias_provider()
2064            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2065        && is_namespace_scope_type_reference(complete)
2066        && namespace_import_can_name_nested_target(complete, ctx)
2067    {
2068        return Some(node);
2069    }
2070
2071    let target_owner = ctx
2072        .analyzer
2073        .parent_of(&ctx.spec.target)
2074        .filter(CodeUnit::is_class);
2075    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2076        let target_components = canonical_cpp_scope_components(target_owner);
2077        if owner.global {
2078            target_components == owner.names
2079        } else {
2080            target_components.ends_with(&owner.names)
2081        }
2082    });
2083    let target_member_visible = |target_owner: &CodeUnit| {
2084        ctx.visibility
2085            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2086            .into_iter()
2087            .any(|candidate| {
2088                same_visible_symbol(candidate, &ctx.spec.target)
2089                    && (ctx
2090                        .visibility
2091                        .external_type_candidate_guard_compatible_in_context(
2092                            &ctx.analyzer,
2093                            ctx.file,
2094                            candidate,
2095                            complete,
2096                        )
2097                        || (ctx
2098                            .visibility
2099                            .is_exhaustive_same_fqn_type_declaration_family(
2100                                &ctx.analyzer,
2101                                ctx.file,
2102                                candidate,
2103                            )
2104                            && ctx.visibility.external_type_candidate_visible_in_context(
2105                                &ctx.analyzer,
2106                                ctx.file,
2107                                candidate,
2108                                complete,
2109                            )))
2110            })
2111    };
2112
2113    if owner_spelling_can_name_target
2114        && (is_namespace_scope_type_reference(complete)
2115            || (is_compound_type_reference(complete)
2116                && namespace_import_can_name_nested_target(complete, ctx)))
2117        && let Some(target_owner) = target_owner.as_ref()
2118        && let LexicalTypeResolution::Resolved { unit, .. } =
2119            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2120                complete,
2121                &owner.names,
2122                owner.global,
2123                &ctx.analyzer,
2124                ctx.visibility,
2125                &ctx.ordinary_type_imports,
2126                ctx.file,
2127                ctx.source,
2128                Some(&ctx.lexical_scope_cache),
2129            )
2130        && ctx
2131            .visibility
2132            .same_template_owner_identity(&unit, target_owner)
2133        && target_member_visible(target_owner)
2134    {
2135        return Some(node);
2136    }
2137
2138    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2139    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2140    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2141        &ctx.analyzer,
2142        ctx.file,
2143        &owner.names,
2144        owner.global,
2145        &lexical_scope,
2146    );
2147    let owner_unit = match owner_resolution {
2148        LexicalTypeResolution::Resolved { unit, .. } => unit,
2149        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2150            ctx.visibility.inherited_injected_class_owner(
2151                &ctx.analyzer,
2152                ctx.file,
2153                &enclosing_owner,
2154                owner.names.first()?,
2155            )?
2156        }
2157        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2158    };
2159    let name = node_text(node, ctx.source);
2160    ctx.visibility
2161        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2162        .into_iter()
2163        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2164        .then_some(complete)
2165}
2166
2167fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2168    // Namespace-scope nested classes need a terminal edge when the complete
2169    // qualified node is the only structured evidence (as in #2213). In
2170    // parameters, fields, and callable bodies the enclosing type node already
2171    // owns the exact range; adding its terminal would duplicate that edge.
2172    let mut current = node.parent();
2173    while let Some(parent) = current {
2174        match parent.kind() {
2175            "call_expression" | "new_expression" => return true,
2176            "parameter_declaration"
2177            | "optional_parameter_declaration"
2178            | "field_declaration"
2179            | "function_definition"
2180            | "function_declarator"
2181            | "class_specifier"
2182            | "struct_specifier"
2183            | "union_specifier"
2184            | "compound_statement" => return false,
2185            "declaration" => {
2186                let mut cursor = parent.walk();
2187                if parent
2188                    .named_children(&mut cursor)
2189                    .any(|child| child.kind() == "function_declarator")
2190                {
2191                    return false;
2192                }
2193            }
2194            _ => {}
2195        }
2196        current = parent.parent();
2197    }
2198    true
2199}
2200
2201fn is_compound_type_reference(node: Node<'_>) -> bool {
2202    let mut current = node.parent();
2203    while let Some(parent) = current {
2204        match parent.kind() {
2205            "compound_statement" => return true,
2206            "namespace_definition" | "translation_unit" => return false,
2207            _ => current = parent.parent(),
2208        }
2209    }
2210    false
2211}
2212
2213fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2214    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2215        return false;
2216    };
2217    effective_using_bindings_for_name(
2218        ctx.visibility,
2219        &ctx.ordinary_type_imports,
2220        ctx.file,
2221        node,
2222        ctx.source,
2223        target_owner.identifier(),
2224    )
2225    .iter()
2226    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2227}
2228
2229/// Recover the class-template qualifier in a dependent leading return type of
2230/// an out-of-line member definition.
2231///
2232/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2233/// `Owner`: one in the return type and one in the callable declarator. The
2234/// latter already has authoritative indexed owner resolution. Reuse that
2235/// structured owner evidence for the former when both template names agree;
2236/// resolving `Owner<T>::Alias` as one type would instead select terminal
2237/// `Alias` and discard the qualifier.
2238fn out_of_line_dependent_return_template_owner<'tree>(
2239    node: Node<'tree>,
2240    ctx: &ScanCtx<'_>,
2241) -> Option<Node<'tree>> {
2242    if node.kind() != "template_type" {
2243        return None;
2244    }
2245    let template_name = template_reference_name_node(node)?;
2246    let mut scope = node;
2247    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2248        matches!(
2249            parent.kind(),
2250            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2251        ) && parent.child_by_field_name("name") == Some(scope)
2252    }) {
2253        scope = parent;
2254    }
2255    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2256        matches!(
2257            parent.kind(),
2258            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2259        ) && parent.child_by_field_name("scope") == Some(scope)
2260    })?;
2261    let mut function = ctx.ancestry.parent(qualified);
2262    let function = loop {
2263        let candidate = function?;
2264        if candidate.kind() == "function_definition" {
2265            break candidate;
2266        }
2267        function = ctx.ancestry.parent(candidate);
2268    };
2269    let return_type = function.child_by_field_name("type")?;
2270    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2271        return None;
2272    }
2273    let declarator_name = function
2274        .child_by_field_name("declarator")
2275        .and_then(declarator_name_node)?;
2276    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2277        return None;
2278    }
2279    let resolved_owner_matches = out_of_line_member_definition_owner(
2280        &ctx.analyzer,
2281        ctx.visibility,
2282        ctx.file,
2283        ctx.source,
2284        declarator_name,
2285    )
2286    .is_some_and(|owners| {
2287        owners
2288            .owners
2289            .iter()
2290            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2291    });
2292    let indexed_owner_matches =
2293        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2294    let target_guided_owner_matches =
2295        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2296    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2297        .then_some(template_name)
2298}
2299
2300/// Use the indexed callable identity when a malformed namespace sentinel
2301/// prevents lexical lookup of an out-of-line template owner.
2302fn indexed_out_of_line_template_owner_hit<'tree>(
2303    node: Node<'tree>,
2304    ctx: &ScanCtx<'_>,
2305) -> Option<Node<'tree>> {
2306    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2307        || !is_declaration_name(node)
2308    {
2309        return None;
2310    }
2311    let mut function = ctx.ancestry.parent(node);
2312    let function = loop {
2313        let candidate = function?;
2314        if candidate.kind() == "function_definition" {
2315            break candidate;
2316        }
2317        function = ctx.ancestry.parent(candidate);
2318    };
2319    if function
2320        .child_by_field_name("declarator")
2321        .and_then(declarator_name_node)
2322        != Some(node)
2323    {
2324        return None;
2325    }
2326    let target = physically_visible_type_target(ctx)?;
2327    let qualified = qualified_owner_components(node, ctx.source)?;
2328    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2329        return None;
2330    }
2331    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2332        != canonical_cpp_scope_components(target)
2333    {
2334        return None;
2335    }
2336    let owner = qualified.nodes.last().copied()?;
2337    let template = if owner.kind() == "template_type" {
2338        owner
2339    } else {
2340        owner
2341            .parent()
2342            .filter(|parent| parent.kind() == "template_type")?
2343    };
2344    template_reference_name_node(template)
2345}
2346
2347fn push_guarded_owner_hit(
2348    owner_node: Node<'_>,
2349    owner: &CodeUnit,
2350    reference: Node<'_>,
2351    ctx: &mut ScanCtx<'_>,
2352) {
2353    if ctx
2354        .visibility
2355        .external_type_candidate_guard_compatible_in_context(
2356            &ctx.analyzer,
2357            ctx.file,
2358            owner,
2359            reference,
2360        )
2361    {
2362        push_hit(owner_node, ctx);
2363    } else {
2364        push_unproven_hit(owner_node, ctx);
2365    }
2366}
2367
2368/// Preserve a direct template-alias reference when a macro namespace sentinel
2369/// makes tree-sitter drop the first source path component.
2370fn target_guided_alias_template_reference<'tree>(
2371    node: Node<'tree>,
2372    ctx: &ScanCtx<'_>,
2373) -> Option<(Node<'tree>, bool)> {
2374    let alias_provider = ctx.analyzer.type_alias_provider()?;
2375    if !matches!(
2376        node.kind(),
2377        "qualified_identifier" | "scoped_type_identifier"
2378    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2379    {
2380        return None;
2381    }
2382    cpp_template_reference_arguments(node, ctx.source)?;
2383    let (components, global) = type_reference_components(node, ctx.source)?;
2384    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2385        return None;
2386    }
2387    let target = physically_visible_type_target(ctx)?;
2388    let target_components = canonical_cpp_scope_components(target);
2389    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2390    let path_matches = if global {
2391        components == target_components
2392            || (!parser_namespace.is_empty()
2393                && target_components.starts_with(&parser_namespace)
2394                && target_components[parser_namespace.len()..] == components)
2395    } else {
2396        let lexical_scope = match enclosing_lexical_scope_components(
2397            node,
2398            &ctx.analyzer,
2399            ctx.visibility,
2400            ctx.file,
2401            ctx.source,
2402        ) {
2403            LexicalScopeResolution::Resolved(scope) => scope,
2404            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2405        };
2406        lexical_component_tiers(&components, false, &lexical_scope)
2407            .any(|scope| scope == target_components)
2408    };
2409    let scoped_candidates = ctx
2410        .visibility
2411        .visible_identifier_candidates(ctx.file, target.identifier())
2412        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2413        .collect::<Vec<_>>();
2414    if !path_matches
2415        || scoped_candidates.is_empty()
2416        || scoped_candidates
2417            .iter()
2418            .any(|candidate| !same_visible_symbol(candidate, target))
2419        || !ctx.visibility.structured_alias_primary_preserves_target(
2420            &ctx.analyzer,
2421            ctx.file,
2422            target,
2423            target,
2424        )
2425    {
2426        return None;
2427    }
2428    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2429        &ctx.analyzer,
2430        ctx.file,
2431        target,
2432        node,
2433    );
2434    Some((node, proven))
2435}
2436
2437/// Tree-sitter can split a macro-qualified member return type into a phantom
2438/// field followed by the real function definition.  The declaration visitor
2439/// discards that phantom field, but its declarator token remains a semantic
2440/// type reference. Resolve it from the recovered or indexed class scope so an
2441/// enclosing-class alias remains distinguishable from same-spelled siblings.
2442fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2443    let name = node_text(return_type, ctx.source);
2444    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2445        return;
2446    }
2447    if physically_visible_type_target(ctx).is_some()
2448        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2449        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2450        && ctx.visibility.external_type_candidate_visible_in_context(
2451            &ctx.analyzer,
2452            ctx.file,
2453            &ctx.spec.target,
2454            return_type,
2455        )
2456    {
2457        *ctx.raw_match_count += 1;
2458        push_type_hit(return_type, ctx);
2459        return;
2460    }
2461    let Some(scope) = ctx
2462        .recovered_sentinel_scope(return_type)
2463        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2464    else {
2465        return;
2466    };
2467    let components = [name.to_string()];
2468    let resolution = ctx.visibility.resolve_type_components_lexically(
2469        &ctx.analyzer,
2470        ctx.file,
2471        &components,
2472        false,
2473        &scope,
2474    );
2475    if let LexicalTypeResolution::Resolved {
2476        unit, candidates, ..
2477    } = resolution
2478        && (same_visible_symbol(&unit, &ctx.spec.target)
2479            || candidates
2480                .iter()
2481                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2482    {
2483        *ctx.raw_match_count += 1;
2484        push_type_hit(return_type, ctx);
2485    }
2486}
2487
2488/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2489/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2490/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2491/// than a type node; ordinary type-reference traversal therefore skips it.
2492fn member_pointer_owner_components<'tree>(
2493    node: Node<'tree>,
2494    source: &str,
2495) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2496    if node.kind() != "qualified_identifier" {
2497        return None;
2498    }
2499    let declarator = node.child_by_field_name("name")?;
2500    if !matches!(
2501        declarator.kind(),
2502        "pointer_type_declarator" | "abstract_pointer_declarator"
2503    ) {
2504        return None;
2505    }
2506    let scope = node.child_by_field_name("scope")?;
2507    // Keep this check entirely structural.  C and C++ share the
2508    // `qualified_identifier` node shape for some recovered declarations, but
2509    // only the C++ member-pointer form has an actual `::` grammar child
2510    // between the owner scope and pointer declarator.  Looking at the source
2511    // slice here would make a recovered C declarator look like a C++ owner
2512    // merely because its bytes happen to contain the same punctuation.
2513    let mut saw_scope = false;
2514    let has_scope_separator = (0..node.child_count()).any(|index| {
2515        let Some(child) = node.child(index) else {
2516            return false;
2517        };
2518        if same_node(child, scope) {
2519            saw_scope = true;
2520            return false;
2521        }
2522        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2523    });
2524    if !has_scope_separator {
2525        return None;
2526    }
2527    let mut nodes = cpp_name_component_nodes(scope)?;
2528    let mut outer = node;
2529    while let Some(parent) = outer.parent()
2530        && parent.kind() == "qualified_identifier"
2531        && parent.child_by_field_name("name") == Some(outer)
2532    {
2533        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2534        prefix.append(&mut nodes);
2535        nodes = prefix;
2536        outer = parent;
2537    }
2538    let names = nodes
2539        .iter()
2540        .map(|component| node_text(*component, source).to_string())
2541        .collect();
2542    Some((
2543        QualifiedOwnerComponents {
2544            nodes,
2545            names,
2546            global: is_globally_qualified_cpp_name(outer),
2547        },
2548        outer,
2549    ))
2550}
2551
2552fn member_pointer_alias_owner_prefix_matches(
2553    node: Node<'_>,
2554    owner: &QualifiedOwnerComponents<'_>,
2555    ctx: &ScanCtx<'_>,
2556) -> bool {
2557    let Some((_, owner_prefix)) = owner.names.split_last() else {
2558        return false;
2559    };
2560    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2561        return false;
2562    };
2563    let recovered_scope = ctx.recovered_sentinel_scope(node);
2564    let resolution = if let Some(recovered_scope) = recovered_scope {
2565        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2566            node,
2567            owner_prefix,
2568            owner.global,
2569            &ctx.analyzer,
2570            ctx.visibility,
2571            &ctx.ordinary_type_imports,
2572            ctx.file,
2573            ctx.source,
2574            &parent,
2575            false,
2576            &recovered_scope,
2577        )
2578    } else {
2579        resolve_type_components_lexically_at_for_target_with_scope_cache(
2580            node,
2581            owner_prefix,
2582            owner.global,
2583            &ctx.analyzer,
2584            ctx.visibility,
2585            &ctx.ordinary_type_imports,
2586            ctx.file,
2587            ctx.source,
2588            &parent,
2589            false,
2590            Some(&ctx.lexical_scope_cache),
2591        )
2592    };
2593    let LexicalTypeResolution::Resolved {
2594        unit, candidates, ..
2595    } = resolution
2596    else {
2597        return false;
2598    };
2599    same_member_pointer_owner_identity(&unit, &parent)
2600        || candidates
2601            .iter()
2602            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2603}
2604
2605fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2606    same_visible_symbol(left, right)
2607        || (left.kind() == right.kind()
2608            && left.fq_name() == right.fq_name()
2609            && left.source() == right.source())
2610}
2611
2612/// Resolve a template type nested in a qualified identifier against a concrete
2613/// type target. The target-guided lexical path intentionally applies a
2614/// structured candidate prefilter; that prefilter cannot see a partial
2615/// specialization until the template arguments have selected it. Resolve the
2616/// complete qualified primary first, then apply the parsed arguments and
2617/// retain the result only when it is the requested target.
2618fn resolve_nested_template_type_for_target(
2619    node: Node<'_>,
2620    ctx: &ScanCtx<'_>,
2621) -> Option<LexicalTypeResolution> {
2622    let reference_node = node
2623        .parent()
2624        .filter(|parent| {
2625            parent.kind() == "qualified_identifier"
2626                && parent.child_by_field_name("name") == Some(node)
2627        })
2628        .unwrap_or(node);
2629    let target_resolution = resolve_type_node_lexically_for_target(
2630        reference_node,
2631        &ctx.analyzer,
2632        ctx.visibility,
2633        &ctx.ordinary_type_imports,
2634        ctx.file,
2635        ctx.source,
2636        &ctx.spec.target,
2637        Some(&ctx.lexical_scope_cache),
2638        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2639    );
2640    if let LexicalTypeResolution::Resolved {
2641        unit, candidates, ..
2642    } = target_resolution
2643        && template_reference_candidates_select_target(
2644            reference_node,
2645            &candidates,
2646            &ctx.analyzer,
2647            ctx.visibility,
2648            ctx.file,
2649            ctx.source,
2650            &ctx.spec.target,
2651        )
2652    {
2653        return Some(LexicalTypeResolution::Resolved {
2654            unit,
2655            components: Vec::new(),
2656            candidates,
2657        });
2658    }
2659
2660    let normal_resolution = resolve_type_node_lexically(
2661        reference_node,
2662        &ctx.analyzer,
2663        ctx.visibility,
2664        &ctx.ordinary_type_imports,
2665        ctx.file,
2666        ctx.source,
2667    );
2668    let LexicalTypeResolution::Resolved {
2669        unit,
2670        components,
2671        candidates,
2672    } = normal_resolution
2673    else {
2674        return None;
2675    };
2676    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2677    let specialized = ctx
2678        .visibility
2679        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2680        .ok()
2681        .unwrap_or(unit);
2682    (same_visible_symbol(&specialized, &ctx.spec.target)
2683        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2684    .then_some(LexicalTypeResolution::Resolved {
2685        unit: specialized,
2686        components,
2687        candidates,
2688    })
2689}
2690
2691fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2692    let mut reference = node;
2693    while let Some(parent) = ctx.ancestry.parent(reference) {
2694        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2695            && parent.child_by_field_name("name") == Some(reference)
2696            || matches!(
2697                parent.kind(),
2698                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2699            ) && (parent.child_by_field_name("scope") == Some(reference)
2700                || parent.child_by_field_name("name") == Some(reference));
2701        if !owns_component {
2702            break;
2703        }
2704        reference = parent;
2705    }
2706    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2707        return false;
2708    };
2709    if !global
2710        && components.len() > 1
2711        && let Some(alias_type) = local_receiver_alias_type_node(reference, &components[0], ctx)
2712    {
2713        let Ok(Some(owner)) = resolve_receiver_type_node(alias_type, ctx) else {
2714            return false;
2715        };
2716        let mut redirected = canonical_cpp_scope_components(&owner);
2717        redirected.extend_from_slice(&components[1..]);
2718        return type_reference_component_list_may_name_target(
2719            &redirected,
2720            true,
2721            &ctx.analyzer,
2722            ctx.visibility,
2723            ctx.file,
2724            &ctx.spec.target,
2725            &ctx.type_reference_component_names,
2726        );
2727    }
2728    type_reference_component_list_may_name_target(
2729        &components,
2730        global,
2731        &ctx.analyzer,
2732        ctx.visibility,
2733        ctx.file,
2734        &ctx.spec.target,
2735        &ctx.type_reference_component_names,
2736    )
2737}
2738
2739fn type_reference_component_list_may_name_target(
2740    components: &[String],
2741    global: bool,
2742    analyzer: &CppGraphSource<'_>,
2743    visibility: &VisibilityIndex<'_>,
2744    file: &ProjectFile,
2745    target: &CodeUnit,
2746    reference_component_names: &HashSet<String>,
2747) -> bool {
2748    if components.iter().any(|component| {
2749        visibility.type_reference_component_directly_names_target(component, target)
2750    }) {
2751        return true;
2752    }
2753    if components.iter().any(|component| {
2754        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2755    }) {
2756        return true;
2757    }
2758    if components.len() > 1 {
2759        return visibility.qualified_alias_reference_may_reach_target(
2760            analyzer, file, components, global, target,
2761        );
2762    }
2763    components
2764        .iter()
2765        .any(|component| reference_component_names.contains(component))
2766}
2767
2768/// Fast structured admission before a type scan builds its parent and lexical
2769/// indexes. Most inverse candidates merely include the target's header and do
2770/// not contain a reference spelling; the full stateful scan has no work in
2771/// those files. Only maximal grammar type names are tested so a common nested
2772/// alias terminal such as `type` is considered together with its owner.
2773fn file_may_reference_type_target(
2774    root: Node<'_>,
2775    source: &str,
2776    analyzer: &CppGraphSource<'_>,
2777    visibility: &VisibilityIndex<'_>,
2778    file: &ProjectFile,
2779    target: &CodeUnit,
2780    reference_component_names: &HashSet<String>,
2781) -> bool {
2782    visit_file_type_reference_spellings(root, source, |components, global| {
2783        type_reference_component_list_may_name_target(
2784            components,
2785            global,
2786            analyzer,
2787            visibility,
2788            file,
2789            target,
2790            reference_component_names,
2791        )
2792    })
2793}
2794
2795fn visit_file_type_reference_spellings(
2796    root: Node<'_>,
2797    source: &str,
2798    mut visit: impl FnMut(&[String], bool) -> bool,
2799) -> bool {
2800    let mut stopped = false;
2801    walk_named_tree_preorder(root, true, |node| {
2802        if node.kind() == "comment" {
2803            return WalkControl::SkipChildren;
2804        }
2805        if node.kind() == "preproc_arg" {
2806            stopped = object_macro_replacement_type_references(node, source)
2807                .into_iter()
2808                .any(|reference| visit(&reference.components, reference.global));
2809            return if stopped {
2810                WalkControl::Break
2811            } else {
2812                WalkControl::SkipChildren
2813            };
2814        }
2815        if node.kind() == "field_declaration"
2816            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2817        {
2818            let components = [node_text(return_type, source).to_string()];
2819            stopped = visit(&components, false);
2820            if stopped {
2821                return WalkControl::Break;
2822            }
2823        }
2824        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2825            for candidate in [node, type_node] {
2826                let Some((components, global)) = type_reference_components(candidate, source)
2827                else {
2828                    continue;
2829                };
2830                stopped = visit(&components, global);
2831                if stopped {
2832                    return WalkControl::Break;
2833                }
2834            }
2835        }
2836        let Some((components, global)) = type_reference_components(node, source) else {
2837            return WalkControl::Continue;
2838        };
2839        if node.parent().is_some_and(|parent| {
2840            (matches!(parent.kind(), "template_type" | "template_function")
2841                && parent.child_by_field_name("name") == Some(node))
2842                || (matches!(
2843                    parent.kind(),
2844                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2845                ) && (parent.child_by_field_name("scope") == Some(node)
2846                    || parent.child_by_field_name("name") == Some(node)))
2847        }) {
2848            return WalkControl::Continue;
2849        }
2850        stopped = visit(&components, global);
2851        if stopped {
2852            WalkControl::Break
2853        } else {
2854            WalkControl::Continue
2855        }
2856    });
2857    stopped
2858}
2859
2860/// Whether this file decides the reference against the scan target.
2861///
2862/// The last resort of type admission has no structured resolution left: it
2863/// only knows that the spelling mentions the target's name and that the target
2864/// itself is not visible here. That is undecidable when nothing else declares
2865/// the name, but it is a proven negative when the file already makes another
2866/// declaration of the same name visible, because unqualified lookup binds the
2867/// reference to that declaration and the include closure never reaches the
2868/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2869/// the same question before it resolves; asking it again here keeps the
2870/// inverse from admitting a site that forward resolution has already decided
2871/// (#2916).
2872fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2873    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2874        return false;
2875    };
2876    let terminal = components
2877        .last()
2878        .expect("type reference components are non-empty");
2879    if terminal != ctx.spec.target.identifier() {
2880        return false;
2881    }
2882    // A concrete specialization is selected from the template arguments, not
2883    // from the visible primary name, so the name-level candidate set cannot
2884    // decide it. `resolve_type_node_lexically_for_target` makes the same
2885    // exception before its own structured prefilter.
2886    if cpp_template_reference_arguments(node, ctx.source).is_some()
2887        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2888    {
2889        return false;
2890    }
2891    // One C++ type reached through a forward declaration in one header and its
2892    // definition in another is two CodeUnits with different signatures and one
2893    // qualified name. A file that sees either of them sees the target's type,
2894    // so only a candidate under a different qualified name decides the
2895    // reference against it. This is what separates the decoy in an unrelated
2896    // namespace from a redeclaration of the target itself.
2897    if ctx
2898        .visibility
2899        .visible_identifier_candidates(ctx.file, terminal)
2900        .any(|candidate| {
2901            candidate.kind() == ctx.spec.target.kind()
2902                && candidate.fq_name() == ctx.spec.target.fq_name()
2903        })
2904    {
2905        return false;
2906    }
2907    !ctx.visibility
2908        .structured_type_reference_may_resolve_to_target(
2909            &ctx.analyzer,
2910            ctx.file,
2911            std::slice::from_ref(terminal),
2912            false,
2913            &[],
2914            &ctx.spec.target,
2915        )
2916}
2917
2918fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2919    let Some(scope) = node.child_by_field_name("scope") else {
2920        return false;
2921    };
2922    let mut pending = vec![scope];
2923    while let Some(candidate) = pending.pop() {
2924        if candidate.kind() == "template_type" {
2925            return true;
2926        }
2927        if matches!(
2928            candidate.kind(),
2929            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2930        ) {
2931            if let Some(scope) = candidate.child_by_field_name("scope") {
2932                pending.push(scope);
2933            }
2934            if let Some(name) = candidate.child_by_field_name("name") {
2935                pending.push(name);
2936            }
2937        }
2938    }
2939    false
2940}
2941
2942fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2943    ctx.target_group.iter().find(|target| {
2944        same_logical_symbol(target, &ctx.spec.target)
2945            && ctx.visibility.is_physically_visible(ctx.file, target)
2946    })
2947}
2948
2949/// Preserve the terminal token of a resolver-proven alias construction.
2950/// Ordinary qualified types and template aliases retain their full type range.
2951/// Alias identity and the construction context are proven by the resolver.
2952fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
2953    if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
2954        push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
2955    } else {
2956        push_type_hit(type_reference_hit_node(node), ctx);
2957    }
2958}
2959
2960fn resolved_alias_terminal_node<'tree>(
2961    node: Node<'tree>,
2962    candidates: &[CodeUnit],
2963    ctx: &ScanCtx<'_>,
2964) -> Option<Node<'tree>> {
2965    let [candidate] = candidates else {
2966        return None;
2967    };
2968    let alias_provider = ctx.analyzer.type_alias_provider()?;
2969    let candidate_is_alias = alias_provider.is_type_alias(candidate)
2970        && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
2971    let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
2972        && same_visible_symbol(candidate, &ctx.spec.target);
2973    if !(candidate_is_alias || target_is_alias) {
2974        return None;
2975    }
2976    if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
2977        return None;
2978    }
2979    let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
2980        node,
2981        &ctx.spec.target,
2982        &ctx.analyzer,
2983        ctx.visibility,
2984        ctx.file,
2985        ctx.source,
2986    );
2987    if reference
2988        != Some(
2989            crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
2990        )
2991    {
2992        return None;
2993    }
2994    cpp_name_component_nodes(node)?.last().copied()
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 ordinary_binding_lookup_targets(
3274    binding: &OrdinaryTypeImport,
3275    active_bindings: &[&OrdinaryTypeImport],
3276    name: &str,
3277) -> Vec<Vec<String>> {
3278    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3279        return Vec::new();
3280    };
3281    let mut targets = Vec::new();
3282    match binding.target {
3283        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3284        EffectiveUsingTarget::Namespace { .. } => {
3285            let mut stack = vec![qualified.clone()];
3286            let mut visited = HashSet::default();
3287            while let Some(namespace) = stack.pop() {
3288                if !visited.insert(namespace.clone()) {
3289                    continue;
3290                }
3291                let mut target = namespace.clone();
3292                target.push(name.to_string());
3293                targets.push(target);
3294                stack.extend(active_bindings.iter().filter_map(|candidate| {
3295                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3296                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3297                    .then(|| candidate.resolved_target_components.clone())
3298                    .flatten()
3299                }));
3300            }
3301        }
3302    }
3303    targets
3304}
3305
3306fn binding_free_function_candidates(
3307    binding: &OrdinaryTypeImport,
3308    active_bindings: &[&OrdinaryTypeImport],
3309    analyzer: &CppGraphSource<'_>,
3310    visibility: &VisibilityIndex<'_>,
3311    file: &ProjectFile,
3312    name: &str,
3313    reference_byte: usize,
3314) -> Vec<CodeUnit> {
3315    ordinary_binding_lookup_targets(binding, active_bindings, name)
3316        .into_iter()
3317        .flat_map(|target| {
3318            let qualified_name = target.join("::");
3319            visibility
3320                .visible_identifier_candidates(file, name)
3321                .filter(move |candidate| {
3322                    candidate.is_function()
3323                        && type_owner_of(analyzer, candidate).is_none()
3324                        && cpp_name_for(candidate) == qualified_name
3325                        && visibility.declaration_visible_at(
3326                            analyzer,
3327                            file,
3328                            candidate,
3329                            reference_byte,
3330                        )
3331                })
3332                .cloned()
3333        })
3334        .collect()
3335}
3336
3337/// Collapse the candidates unqualified lookup found to one entry per logical
3338/// callable, keeping the first spelling of each.
3339///
3340/// A header declaration and its out-of-line body spell one parameter type
3341/// differently often enough that the persisted signature strings disagree, so
3342/// the string triple alone reports one C++ declaration as an overload set and
3343/// an unproven argument count then turns it into ambiguity.
3344/// `same_logical_callable` answers the string question first and resolves the
3345/// written parameter names only when the strings differ (#2010).
3346fn dedupe_callable_candidates(
3347    candidates: &mut Vec<CodeUnit>,
3348    analyzer: &CppGraphSource<'_>,
3349    visibility: &VisibilityIndex<'_>,
3350) {
3351    let mut deduped = Vec::with_capacity(candidates.len());
3352    for candidate in candidates.drain(..) {
3353        if !deduped
3354            .iter()
3355            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3356        {
3357            deduped.push(candidate);
3358        }
3359    }
3360    *candidates = deduped;
3361}
3362
3363fn resolve_callable_candidates(
3364    candidates: Vec<CodeUnit>,
3365    call_arity: Option<usize>,
3366    reference_byte: usize,
3367    analyzer: &CppGraphSource<'_>,
3368    visibility: &VisibilityIndex<'_>,
3369    file: &ProjectFile,
3370) -> BareCallTargetResolution {
3371    let mut candidates = candidates;
3372    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3373    if candidates.is_empty() {
3374        return BareCallTargetResolution::Missing;
3375    }
3376    let Some(call_arity) = call_arity else {
3377        // An unproven argument count cannot create ambiguity where lookup found
3378        // exactly one name binding: there is nothing to be ambiguous between.
3379        // C has no overloading at all, and a lone C++ candidate is the only
3380        // declaration unqualified lookup reached, so arity cannot pick another
3381        // one (#1811). Keeping it unproven discarded the proven candidate and
3382        // answered `ambiguous` with an empty definition list.
3383        if candidates.len() == 1 {
3384            return BareCallTargetResolution::FreeFunctions(candidates);
3385        }
3386        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3387    };
3388    let applicable = candidates
3389        .into_iter()
3390        .filter(|candidate| {
3391            visibility
3392                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3393                .is_some_and(|arity| arity.accepts(call_arity))
3394        })
3395        .collect::<Vec<_>>();
3396    if applicable.is_empty() {
3397        BareCallTargetResolution::CallableShadow
3398    } else {
3399        BareCallTargetResolution::FreeFunctions(applicable)
3400    }
3401}
3402
3403fn deduplicate_type_import_candidates(
3404    candidates: Vec<(CodeUnit, Vec<String>)>,
3405) -> Vec<(CodeUnit, Vec<String>)> {
3406    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3407    for candidate in candidates {
3408        if !logical
3409            .iter()
3410            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3411        {
3412            logical.push(candidate);
3413        }
3414    }
3415    logical
3416}
3417
3418fn resolve_direct_type_candidates(
3419    candidates: Vec<(CodeUnit, Vec<String>)>,
3420    analyzer: &CppGraphSource<'_>,
3421    visibility: &VisibilityIndex<'_>,
3422    file: &ProjectFile,
3423) -> BareCallTargetResolution {
3424    let logical = deduplicate_type_import_candidates(candidates);
3425    let [(target, components)] = logical.as_slice() else {
3426        return if logical.is_empty() {
3427            BareCallTargetResolution::Missing
3428        } else {
3429            BareCallTargetResolution::Ambiguous
3430        };
3431    };
3432    match visibility
3433        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3434    {
3435        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3436        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3437        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3438    }
3439}
3440
3441/// Resolve a direct using-declaration in the nearest concrete block before
3442/// class-member lookup. A block declaration such as `using std::swap;` adds
3443/// that name to the block scope and hides a same-named member. If the imported
3444/// target is not indexed, retain its structured path as boundary evidence.
3445#[allow(clippy::too_many_arguments)]
3446pub fn resolve_block_using_call_target(
3447    call: Node<'_>,
3448    function: Node<'_>,
3449    analyzer: &CppGraphSource<'_>,
3450    visibility: &VisibilityIndex<'_>,
3451    ordinary_type_imports: &OrdinaryTypeImportCell,
3452    file: &ProjectFile,
3453    source: &str,
3454) -> Option<BlockUsingCallTargetResolution> {
3455    if !matches!(function.kind(), "identifier" | "template_function") {
3456        return None;
3457    }
3458    let name = node_text(function_terminal_node(function), source);
3459    if name.is_empty() {
3460        return None;
3461    }
3462    let bindings = effective_using_bindings_for_name(
3463        visibility,
3464        ordinary_type_imports,
3465        file,
3466        function,
3467        source,
3468        name,
3469    );
3470    let block_bindings = bindings
3471        .iter()
3472        .filter(|binding| {
3473            binding.namespace_scope.is_none()
3474                && binding.block_scope
3475                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3476        })
3477        .collect::<Vec<_>>();
3478    if block_bindings.is_empty() {
3479        return None;
3480    }
3481    let lexical_scope =
3482        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3483            LexicalScopeResolution::Resolved(scope) => scope,
3484            LexicalScopeResolution::Ambiguous => {
3485                return Some(BlockUsingCallTargetResolution::Ambiguous);
3486            }
3487            LexicalScopeResolution::Missing => return None,
3488        };
3489    let reference_guards = preprocessor_guard_environment(function, source);
3490    let active = block_bindings
3491        .into_iter()
3492        .filter(|binding| {
3493            effective_using_binding_active(
3494                binding,
3495                function,
3496                &lexical_scope,
3497                reference_guards.as_ref(),
3498                visibility,
3499                file,
3500            )
3501        })
3502        .collect::<Vec<_>>();
3503    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3504    let at_tier = active
3505        .into_iter()
3506        .filter(|binding| binding.scope_depth == depth)
3507        .collect::<Vec<_>>();
3508    let callable_candidates = at_tier
3509        .iter()
3510        .flat_map(|binding| {
3511            binding_free_function_candidates(
3512                binding,
3513                &[],
3514                analyzer,
3515                visibility,
3516                file,
3517                name,
3518                call.start_byte(),
3519            )
3520        })
3521        .collect::<Vec<_>>();
3522    if !callable_candidates.is_empty() {
3523        return Some(BlockUsingCallTargetResolution::Target(
3524            resolve_callable_candidates(
3525                callable_candidates,
3526                visibility.call_arity_evidence(file, call, source).exact(),
3527                call.start_byte(),
3528                analyzer,
3529                visibility,
3530                file,
3531            ),
3532        ));
3533    }
3534    let type_candidates = at_tier
3535        .iter()
3536        .flat_map(|binding| {
3537            binding_type_candidates(
3538                binding,
3539                &[],
3540                analyzer,
3541                visibility,
3542                file,
3543                name,
3544                None,
3545                call.start_byte(),
3546            )
3547        })
3548        .collect::<Vec<_>>();
3549    if !type_candidates.is_empty() {
3550        return Some(BlockUsingCallTargetResolution::Target(
3551            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3552        ));
3553    }
3554
3555    let mut unindexed = Vec::new();
3556    for binding in at_tier {
3557        let Some(components) = binding.resolved_target_components.as_ref() else {
3558            continue;
3559        };
3560        if !unindexed.contains(components) {
3561            unindexed.push(components.clone());
3562        }
3563    }
3564    match unindexed.as_slice() {
3565        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3566        [] => None,
3567        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3568    }
3569}
3570
3571#[allow(clippy::too_many_arguments)]
3572pub fn resolve_bare_call_target(
3573    call: Node<'_>,
3574    function: Node<'_>,
3575    analyzer: &CppGraphSource<'_>,
3576    visibility: &VisibilityIndex<'_>,
3577    ordinary_type_imports: &OrdinaryTypeImportCell,
3578    file: &ProjectFile,
3579    source: &str,
3580) -> BareCallTargetResolution {
3581    if !matches!(function.kind(), "identifier" | "template_function") {
3582        return BareCallTargetResolution::Missing;
3583    }
3584    let terminal = function_terminal_node(function);
3585    let name = node_text(terminal, source);
3586    if name.is_empty() {
3587        return BareCallTargetResolution::Missing;
3588    }
3589    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3590    let lexical_scope =
3591        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3592            LexicalScopeResolution::Resolved(scope) => scope,
3593            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3594            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3595        };
3596    let type_resolution = resolve_type_node_lexically(
3597        function,
3598        analyzer,
3599        visibility,
3600        ordinary_type_imports,
3601        file,
3602        source,
3603    );
3604    let type_components = match &type_resolution {
3605        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3606        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3607    };
3608    let direct_type_resolution = visibility.resolve_type_components_lexically(
3609        analyzer,
3610        file,
3611        &[name.to_string()],
3612        false,
3613        &lexical_scope,
3614    );
3615    let direct_type_components = match &direct_type_resolution {
3616        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3617        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3618    };
3619    let has_explicit_template_arguments =
3620        cpp_template_reference_arguments(function, source).is_some();
3621    let bindings = effective_using_bindings_for_name(
3622        visibility,
3623        ordinary_type_imports,
3624        file,
3625        function,
3626        source,
3627        name,
3628    );
3629    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3630    // step re-descends from the root (#1927): with no bindings both filters
3631    // below select nothing, so the environment is never consulted.
3632    let function_guards = if bindings.is_empty() {
3633        None
3634    } else {
3635        preprocessor_guard_environment(function, source)
3636    };
3637    let active_bindings = bindings
3638        .iter()
3639        .filter(|binding| {
3640            effective_using_binding_active(
3641                binding,
3642                function,
3643                &lexical_scope,
3644                function_guards.as_ref(),
3645                visibility,
3646                file,
3647            )
3648        })
3649        .collect::<Vec<_>>();
3650    let transitive_bindings = bindings
3651        .iter()
3652        .filter(|binding| {
3653            effective_using_binding_guards_active(
3654                binding,
3655                function.start_byte(),
3656                function_guards.as_ref(),
3657                visibility,
3658                file,
3659            ) && (binding.namespace_scope.is_some()
3660                || (binding.scope_start <= function.start_byte()
3661                    && function.end_byte() <= binding.scope_end))
3662        })
3663        .collect::<Vec<_>>();
3664    let mut concrete_depths = active_bindings
3665        .iter()
3666        .filter(|binding| binding.namespace_scope.is_none())
3667        .map(|binding| binding.scope_depth)
3668        .collect::<Vec<_>>();
3669    concrete_depths.sort_unstable();
3670    concrete_depths.dedup();
3671    for depth in concrete_depths.into_iter().rev() {
3672        let at_tier = active_bindings
3673            .iter()
3674            .copied()
3675            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3676        let direct = at_tier
3677            .clone()
3678            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3679            .flat_map(|binding| {
3680                binding_free_function_candidates(
3681                    binding,
3682                    &transitive_bindings,
3683                    analyzer,
3684                    visibility,
3685                    file,
3686                    name,
3687                    call.start_byte(),
3688                )
3689            })
3690            .collect::<Vec<_>>();
3691        if !direct.is_empty() {
3692            return resolve_callable_candidates(
3693                direct,
3694                call_arity,
3695                call.start_byte(),
3696                analyzer,
3697                visibility,
3698                file,
3699            );
3700        }
3701        let direct_types = at_tier
3702            .clone()
3703            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3704            .flat_map(|binding| {
3705                binding_type_candidates(
3706                    binding,
3707                    &transitive_bindings,
3708                    analyzer,
3709                    visibility,
3710                    file,
3711                    name,
3712                    None,
3713                    call.start_byte(),
3714                )
3715            })
3716            .collect::<Vec<_>>();
3717        if !direct_types.is_empty() {
3718            // `resolve_direct_type_candidates` never consults the argument
3719            // count: it answers the one type the name binds to, or reports the
3720            // competing types. An unknown count therefore cannot make this
3721            // ambiguous (#1812).
3722            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3723        }
3724        let directives = at_tier
3725            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3726            .flat_map(|binding| {
3727                binding_free_function_candidates(
3728                    binding,
3729                    &transitive_bindings,
3730                    analyzer,
3731                    visibility,
3732                    file,
3733                    name,
3734                    call.start_byte(),
3735                )
3736            })
3737            .collect::<Vec<_>>();
3738        if !directives.is_empty() {
3739            return resolve_callable_candidates(
3740                directives,
3741                call_arity,
3742                call.start_byte(),
3743                analyzer,
3744                visibility,
3745                file,
3746            );
3747        }
3748    }
3749    for prefix_len in (0..=lexical_scope.len()).rev() {
3750        let mut qualified = lexical_scope[..prefix_len].to_vec();
3751        qualified.push(name.to_string());
3752        let same_name_resolves_to_type = direct_type_components
3753            .is_some_and(|components| components == qualified.as_slice())
3754            || type_components.is_some_and(|components| components == qualified.as_slice());
3755        let mut direct = visibility
3756            .visible_identifier_candidates(file, name)
3757            .filter(|candidate| {
3758                candidate.is_function()
3759                    && type_owner_of(analyzer, candidate).is_none()
3760                    && !(same_name_resolves_to_type
3761                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3762                            || has_explicit_template_arguments
3763                                && visibility
3764                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3765                    && cpp_name_for(candidate) == qualified.join("::")
3766                    && if analyzer.reference_uses_c_semantics(file) {
3767                        visibility.declaration_visible_for_c_forward_call(
3768                            analyzer,
3769                            file,
3770                            candidate,
3771                            call.start_byte(),
3772                        )
3773                    } else {
3774                        visibility.declaration_visible_at(
3775                            analyzer,
3776                            file,
3777                            candidate,
3778                            call.start_byte(),
3779                        )
3780                    }
3781            })
3782            .cloned()
3783            .collect::<Vec<_>>();
3784        let at_tier = active_bindings.iter().copied().filter(|binding| {
3785            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3786        });
3787        direct.extend(
3788            at_tier
3789                .clone()
3790                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3791                .flat_map(|binding| {
3792                    binding_free_function_candidates(
3793                        binding,
3794                        &transitive_bindings,
3795                        analyzer,
3796                        visibility,
3797                        file,
3798                        name,
3799                        call.start_byte(),
3800                    )
3801                }),
3802        );
3803        if !direct.is_empty() {
3804            return resolve_callable_candidates(
3805                direct,
3806                call_arity,
3807                call.start_byte(),
3808                analyzer,
3809                visibility,
3810                file,
3811            );
3812        }
3813        let mut direct_types = at_tier
3814            .clone()
3815            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3816            .flat_map(|binding| {
3817                binding_type_candidates(
3818                    binding,
3819                    &transitive_bindings,
3820                    analyzer,
3821                    visibility,
3822                    file,
3823                    name,
3824                    None,
3825                    call.start_byte(),
3826                )
3827            })
3828            .collect::<Vec<_>>();
3829        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3830            && let LexicalTypeResolution::Resolved {
3831                unit, components, ..
3832            } = &direct_type_resolution
3833        {
3834            direct_types.push((unit.clone(), components.clone()));
3835        }
3836        if !direct_types.is_empty() {
3837            // `resolve_direct_type_candidates` never consults the argument
3838            // count: it answers the one type the name binds to, or reports the
3839            // competing types. An unknown count therefore cannot make this
3840            // ambiguous (#1812).
3841            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3842        }
3843        let directives = at_tier
3844            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3845            .flat_map(|binding| {
3846                binding_free_function_candidates(
3847                    binding,
3848                    &transitive_bindings,
3849                    analyzer,
3850                    visibility,
3851                    file,
3852                    name,
3853                    call.start_byte(),
3854                )
3855            })
3856            .collect::<Vec<_>>();
3857        if !directives.is_empty() {
3858            return resolve_callable_candidates(
3859                directives,
3860                call_arity,
3861                call.start_byte(),
3862                analyzer,
3863                visibility,
3864                file,
3865            );
3866        }
3867        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3868            // The lexical type resolution below already answers with the single
3869            // type, or with its own ambiguity verdict; the argument count adds
3870            // nothing to that decision (#1812).
3871            return match type_resolution {
3872                LexicalTypeResolution::Resolved { unit, .. } => {
3873                    BareCallTargetResolution::Type(unit)
3874                }
3875                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3876                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3877            };
3878        }
3879    }
3880    // Every lookup tier is exhausted: no callable and no type candidate was
3881    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
3882    // at all, and its early return in get_definition preempted the same-file
3883    // macro fallback - so a call to a macro defined in the referencing file
3884    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
3885    // resolve once an unresolvable include made the argument count unknown.
3886    // A no-candidate outcome is Missing, which is what makes the fallback
3887    // reachable (#1812).
3888    match type_resolution {
3889        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3890        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3891        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3892    }
3893}
3894
3895fn static_qualifier_type_scopes<'tree>(
3896    node: Node<'tree>,
3897    ctx: &ScanCtx<'_>,
3898) -> Option<Vec<Node<'tree>>> {
3899    if !matches!(
3900        node.kind(),
3901        "qualified_identifier" | "scoped_type_identifier"
3902    ) {
3903        return None;
3904    }
3905    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
3906    // this root contains every structured component needed for prefix lookup.
3907    debug_assert!(!is_nested_type_node(node));
3908    let qualified = qualified_owner_components(node, ctx.source)?;
3909    static_qualifier_type_scopes_for_components(node, qualified, ctx)
3910}
3911
3912fn static_qualifier_type_scopes_for_components<'tree>(
3913    node: Node<'tree>,
3914    qualified: QualifiedOwnerComponents<'tree>,
3915    ctx: &ScanCtx<'_>,
3916) -> Option<Vec<Node<'tree>>> {
3917    if !qualified.global
3918        && let Some(name) = qualified.names.first()
3919        && let Some(alias_type) = local_receiver_alias_type_node(node, name, ctx)
3920    {
3921        return match resolve_receiver_type_node(alias_type, ctx) {
3922            Ok(Some(owner)) if same_visible_symbol(&owner, &ctx.spec.target) => {
3923                Some(vec![qualified.nodes[0]])
3924            }
3925            _ => None,
3926        };
3927    }
3928    let mut matches = Vec::new();
3929    let mut inherited_injected_name_is_shadowed = false;
3930    for component_count in 1..=qualified.names.len() {
3931        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3932            node,
3933            &qualified.names[..component_count],
3934            qualified.global,
3935            &ctx.analyzer,
3936            ctx.visibility,
3937            &ctx.ordinary_type_imports,
3938            ctx.file,
3939            ctx.source,
3940            &ctx.spec.target,
3941            false,
3942            Some(&ctx.lexical_scope_cache),
3943        );
3944        match resolution {
3945            LexicalTypeResolution::Resolved {
3946                unit, candidates, ..
3947            } if (!ctx
3948                .analyzer
3949                .type_alias_provider()
3950                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3951                || ctx.visibility.external_type_candidate_visible_in_context(
3952                    &ctx.analyzer,
3953                    ctx.file,
3954                    &ctx.spec.target,
3955                    node,
3956                ))
3957                && (same_visible_symbol(&unit, &ctx.spec.target)
3958                    || candidates
3959                        .iter()
3960                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3961                && target_alias_candidates_visible(&candidates, node, ctx) =>
3962            {
3963                let matched =
3964                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3965                if !template_type_component_preserves_target(matched, &candidates, ctx) {
3966                    continue;
3967                }
3968                if !matches.iter().any(|existing: &Node<'_>| {
3969                    existing.start_byte() == matched.start_byte()
3970                        && existing.end_byte() == matched.end_byte()
3971                }) {
3972                    matches.push(matched);
3973                }
3974            }
3975            // The ordinary lexical resolver can remain ambiguous when the
3976            // qualified terminal is an alias whose canonical target is not
3977            // indexed (for example, `Hash::Digest` aliases an external
3978            // `std::array`). The target-guided path below still requires one
3979            // physically visible logical class for every emitted prefix.
3980            LexicalTypeResolution::Ambiguous => {
3981                return (!inherited_injected_name_is_shadowed)
3982                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3983                    .flatten()
3984                    .map(|scope| vec![scope])
3985                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3986            }
3987            LexicalTypeResolution::Resolved { .. } => {
3988                if let Some(matched) =
3989                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3990                {
3991                    matches.push(matched);
3992                }
3993                inherited_injected_name_is_shadowed |= component_count == 1;
3994            }
3995            LexicalTypeResolution::Missing => {
3996                if let Some(matched) =
3997                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3998                {
3999                    matches.push(matched);
4000                }
4001            }
4002        }
4003    }
4004    if matches.is_empty() {
4005        (!inherited_injected_name_is_shadowed)
4006            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4007            .flatten()
4008            .map(|scope| vec![scope])
4009            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
4010    } else {
4011        Some(matches)
4012    }
4013}
4014
4015/// Recover a class owner in a qualified expression when guard-aware lookup
4016/// cannot prove the owner. Keep the hit on the owner component, not the member.
4017enum TargetGuidedQualifiedValueOwnerHit<'tree> {
4018    Proven(Node<'tree>),
4019    Unproven(Node<'tree>),
4020}
4021
4022fn target_guided_qualified_value_owner_hit<'tree>(
4023    node: Node<'tree>,
4024    ctx: &ScanCtx<'_>,
4025) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
4026    let target = physically_visible_type_target(ctx)?;
4027    if !target.is_class() {
4028        return None;
4029    }
4030    let qualified = qualified_owner_components(node, ctx.source)?;
4031    let lexical_scope = match enclosing_lexical_scope_components(
4032        node,
4033        &ctx.analyzer,
4034        ctx.visibility,
4035        ctx.file,
4036        ctx.source,
4037    ) {
4038        LexicalScopeResolution::Resolved(scope) => scope,
4039        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4040            enclosing_namespace_components(node, ctx.source)
4041        }
4042    };
4043    let LexicalTypeResolution::Resolved {
4044        unit, candidates, ..
4045    } = ctx.visibility.resolve_type_components_lexically_for_target(
4046        &ctx.analyzer,
4047        ctx.file,
4048        &qualified.names,
4049        qualified.global,
4050        &lexical_scope,
4051        target,
4052    )
4053    else {
4054        return None;
4055    };
4056    let scope = qualified.nodes.last().copied()?;
4057    // Lexical component lookup does not consider source order, so it can
4058    // select a declaration that follows the reference. The forward selection
4059    // proves the owner only when that declaration is visible at the reference.
4060    let forward_uniquely_selects_target = match ctx
4061        .visibility
4062        .resolve_type_components_lexically_for_forward(
4063            &ctx.analyzer,
4064            ctx.file,
4065            &qualified.names,
4066            qualified.global,
4067            &lexical_scope,
4068        ) {
4069        LexicalTypeResolution::Resolved {
4070            unit, candidates, ..
4071        } if candidates.len() == 1 => {
4072            same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4073                && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4074                && ctx.visibility.external_type_candidate_visible_in_context(
4075                    &ctx.analyzer,
4076                    ctx.file,
4077                    &candidates[0],
4078                    scope,
4079                )
4080        }
4081        LexicalTypeResolution::Resolved { .. }
4082        | LexicalTypeResolution::Ambiguous
4083        | LexicalTypeResolution::Missing => false,
4084    };
4085    if forward_uniquely_selects_target
4086        || (same_visible_symbol(&unit, target)
4087            && !candidates.is_empty()
4088            && candidates
4089                .iter()
4090                .all(|candidate| same_visible_symbol(candidate, target)))
4091    {
4092        Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4093    } else if candidates
4094        .iter()
4095        .any(|candidate| same_visible_symbol(candidate, target))
4096    {
4097        Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4098    } else {
4099        None
4100    }
4101}
4102
4103/// Resolve a nested class-owned alias when the indexed alias path is not a
4104/// standalone type candidate. The C++ index stores `basic_json::type_error`
4105/// as a synthetic child of `basic_json`, while source can qualify it through
4106/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4107/// then canonicalize the structured member alias against the requested type.
4108fn target_guided_nested_alias_type_scope<'tree>(
4109    node: Node<'tree>,
4110    qualified: &QualifiedOwnerComponents<'tree>,
4111    component_count: usize,
4112    ctx: &ScanCtx<'_>,
4113) -> Option<Node<'tree>> {
4114    if component_count < 2 {
4115        return None;
4116    }
4117    let (owner_components, member_name) =
4118        qualified.names[..component_count].split_at(component_count - 1);
4119    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4120        node,
4121        owner_components,
4122        qualified.global,
4123        &ctx.analyzer,
4124        ctx.visibility,
4125        &ctx.ordinary_type_imports,
4126        ctx.file,
4127        ctx.source,
4128    ) else {
4129        return None;
4130    };
4131    let member_name = member_name.first()?;
4132    let alias_provider = ctx.analyzer.type_alias_provider()?;
4133    ctx.visibility
4134        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4135        .into_iter()
4136        .filter(|member| alias_provider.is_type_alias(member))
4137        .find(|member| {
4138            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4139                &ctx.analyzer,
4140                ctx.file,
4141                member,
4142                node,
4143            ) || ctx
4144                .visibility
4145                .external_type_candidate_guard_compatible_in_context(
4146                    &ctx.analyzer,
4147                    ctx.file,
4148                    member,
4149                    node,
4150                );
4151            if !member_visible {
4152                return false;
4153            }
4154            same_visible_symbol(member, &ctx.spec.target)
4155                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4156        })
4157        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4158}
4159
4160fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4161    if ctx.visibility.structured_class_alias_resolves_to_target(
4162        &ctx.analyzer,
4163        ctx.file,
4164        candidate,
4165        &ctx.spec.target,
4166    ) {
4167        return ctx.spec.target.clone();
4168    }
4169    let structured = ctx
4170        .visibility
4171        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4172    structured.unwrap_or_else(|| candidate.clone())
4173}
4174
4175/// Preserve the alias component of a qualified reference when the alias target
4176/// is a dependent nested type and forward lookup retains its primary template
4177/// as the bounded identity.
4178fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4179    ctx.visibility
4180        .external_type_candidate_guard_compatible_in_context(
4181            &ctx.analyzer,
4182            ctx.file,
4183            candidate,
4184            node,
4185        )
4186}
4187
4188fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4189    ctx.visibility.is_physically_visible(ctx.file, candidate)
4190        && class_alias_guard_compatible(candidate, node, ctx)
4191}
4192
4193fn class_alias_path_preserves_target(
4194    candidate: &CodeUnit,
4195    target: &CodeUnit,
4196    ctx: &ScanCtx<'_>,
4197) -> bool {
4198    ctx.visibility.structured_class_alias_path_preserves_target(
4199        &ctx.analyzer,
4200        ctx.file,
4201        candidate,
4202        target,
4203    )
4204}
4205
4206fn target_guided_dependent_alias_qualifier_scope<'tree>(
4207    node: Node<'tree>,
4208    ctx: &ScanCtx<'_>,
4209) -> Option<Node<'tree>> {
4210    if !matches!(
4211        node.kind(),
4212        "qualified_identifier" | "scoped_type_identifier"
4213    ) {
4214        return None;
4215    }
4216    let target = physically_visible_type_target(ctx)?;
4217    let alias_provider = ctx.analyzer.type_alias_provider()?;
4218    if !target.is_class() || alias_provider.is_type_alias(target) {
4219        return None;
4220    }
4221    let nodes = cpp_name_component_nodes(node)?;
4222    let names = nodes
4223        .iter()
4224        .map(|component| node_text(*component, ctx.source).to_string())
4225        .collect::<Vec<_>>();
4226    let global = is_globally_qualified_cpp_name(node);
4227    let lexical_scope = match enclosing_lexical_scope_components(
4228        node,
4229        &ctx.analyzer,
4230        ctx.visibility,
4231        ctx.file,
4232        ctx.source,
4233    ) {
4234        LexicalScopeResolution::Resolved(scope) => scope,
4235        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4236            enclosing_namespace_components(node, ctx.source)
4237        }
4238    };
4239    for component_count in 1..=names.len() {
4240        let components = &names[..component_count];
4241        let name = components.last()?;
4242        let candidates = ctx
4243            .visibility
4244            .visible_identifier_candidates(ctx.file, name)
4245            .filter(|candidate| alias_provider.is_type_alias(candidate))
4246            .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4247            .filter(|candidate| {
4248                let candidate_components = canonical_cpp_scope_components(candidate);
4249                lexical_component_tiers(components, global, &lexical_scope)
4250                    .any(|tier| tier == candidate_components)
4251                    || (component_count == 1
4252                        && member_alias_owner_matches_reference_for(
4253                            candidate,
4254                            nodes[component_count - 1],
4255                            ctx,
4256                        ))
4257            })
4258            .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4259        let mut aliases: Vec<&CodeUnit> = Vec::new();
4260        for candidate in candidates {
4261            if !aliases
4262                .iter()
4263                .any(|existing| same_logical_symbol(existing, candidate))
4264            {
4265                aliases.push(candidate);
4266            }
4267        }
4268        if aliases.len() == 1 {
4269            return nodes.get(component_count - 1).copied();
4270        }
4271        if aliases.len() > 1 {
4272            return None;
4273        }
4274    }
4275    None
4276}
4277
4278/// Preserve an unqualified class-owned alias when its dependent target path
4279/// retains the requested primary template as the bounded forward identity.
4280fn target_guided_dependent_class_alias_leaf<'tree>(
4281    node: Node<'tree>,
4282    ctx: &ScanCtx<'_>,
4283) -> Option<Node<'tree>> {
4284    if node.kind() != "type_identifier"
4285        || is_declaration_name(node)
4286        || local_type_name_shadows(node, ctx)
4287    {
4288        return None;
4289    }
4290    let target = physically_visible_type_target(ctx)?;
4291    let alias_provider = ctx.analyzer.type_alias_provider()?;
4292    if !target.is_class() || alias_provider.is_type_alias(target) {
4293        return None;
4294    }
4295    let name = node_text(node, ctx.source);
4296    let aliases = ctx
4297        .visibility
4298        .visible_identifier_candidates(ctx.file, name)
4299        .filter(|candidate| alias_provider.is_type_alias(candidate))
4300        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4301        .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4302        .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4303        .collect::<Vec<_>>();
4304    matches!(aliases.as_slice(), [_]).then_some(node)
4305}
4306
4307/// Recover a namespace alias whose guard state blocks ordinary visibility.
4308/// Require one visible canonical target and an exact structured alias path.
4309fn target_guided_unproven_alias_type_reference<'tree>(
4310    node: Node<'tree>,
4311    candidates: &[CodeUnit],
4312    ctx: &ScanCtx<'_>,
4313) -> Option<Node<'tree>> {
4314    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4315    let target = physically_visible_type_target(ctx)?;
4316    if !target.is_class() {
4317        return None;
4318    }
4319    let alias_provider = ctx.analyzer.type_alias_provider()?;
4320    let (components, _) = type_reference_components(node, ctx.source)?;
4321    let hit = template_arguments
4322        .as_ref()
4323        .and_then(|_| template_reference_name_node(node))
4324        .map(function_terminal_node)
4325        .unwrap_or_else(|| function_terminal_node(node));
4326    candidates
4327        .iter()
4328        .filter(|candidate| {
4329            alias_provider.is_type_alias(candidate)
4330                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4331                && canonical_cpp_scope_components(candidate) == components
4332        })
4333        .find(|candidate| {
4334            template_arguments.as_ref().map_or_else(
4335                || {
4336                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4337                        || ctx.visibility.structured_alias_primary_preserves_target(
4338                            &ctx.analyzer,
4339                            ctx.file,
4340                            candidate,
4341                            target,
4342                        )
4343                },
4344                |arguments| {
4345                    ctx.visibility.template_alias_arguments_preserve_target(
4346                        &ctx.analyzer,
4347                        ctx.file,
4348                        candidate,
4349                        arguments,
4350                        target,
4351                    )
4352                },
4353            )
4354        })
4355        .map(|_| hit)
4356}
4357
4358fn target_alias_candidates_visible(
4359    candidates: &[CodeUnit],
4360    reference: Node<'_>,
4361    ctx: &ScanCtx<'_>,
4362) -> bool {
4363    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4364        return true;
4365    };
4366    if candidates.iter().any(|candidate| {
4367        !alias_provider.is_type_alias(candidate)
4368            && ctx.visibility.same_template_member_identity(
4369                &ctx.analyzer,
4370                candidate,
4371                &ctx.spec.target,
4372            )
4373    }) {
4374        return true;
4375    }
4376    let target_aliases = candidates
4377        .iter()
4378        .filter(|candidate| {
4379            alias_provider.is_type_alias(candidate)
4380                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4381        })
4382        .collect::<Vec<_>>();
4383    target_aliases.is_empty()
4384        || target_aliases
4385            .iter()
4386            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4387}
4388
4389fn type_candidate_visible_at_reference(
4390    candidate: &CodeUnit,
4391    reference: Node<'_>,
4392    ctx: &ScanCtx<'_>,
4393) -> bool {
4394    let class_owned_alias = ctx
4395        .analyzer
4396        .type_alias_provider()
4397        .is_some_and(|provider| provider.is_type_alias(candidate))
4398        && ctx
4399            .analyzer
4400            .parent_of(candidate)
4401            .is_some_and(|owner| owner.is_class());
4402    if class_owned_alias {
4403        let conditional_family = ctx
4404            .visibility
4405            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4406        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4407            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4408            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4409        let guard_match = ctx
4410            .visibility
4411            .external_type_candidate_guard_compatible_in_context(
4412                &ctx.analyzer,
4413                ctx.file,
4414                candidate,
4415                reference,
4416            );
4417        let general_match = conditional_family
4418            && ctx.visibility.external_type_candidate_visible_in_context(
4419                &ctx.analyzer,
4420                ctx.file,
4421                candidate,
4422                reference,
4423            );
4424        return owner_match && (guard_match || general_match);
4425    }
4426    ctx.visibility.external_type_candidate_visible_in_context(
4427        &ctx.analyzer,
4428        ctx.file,
4429        candidate,
4430        reference,
4431    )
4432}
4433
4434fn unqualified_reference_selects_inherited_alias(
4435    candidate: &CodeUnit,
4436    reference: Node<'_>,
4437    ctx: &ScanCtx<'_>,
4438) -> bool {
4439    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4440        return false;
4441    };
4442    if global || components.len() != 1 {
4443        return false;
4444    }
4445    matches!(
4446        resolve_type_node_lexically_for_target(
4447            reference,
4448            &ctx.analyzer,
4449            ctx.visibility,
4450            &ctx.ordinary_type_imports,
4451            ctx.file,
4452            ctx.source,
4453            candidate,
4454            Some(&ctx.lexical_scope_cache),
4455            ctx.recovered_sentinel_scope(reference).as_deref(),
4456        ),
4457        LexicalTypeResolution::Resolved {
4458            ref unit,
4459            ref candidates,
4460            ..
4461        } if ctx
4462            .visibility
4463            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4464            || candidates.iter().any(|resolved| {
4465                ctx.visibility.same_template_member_identity(
4466                    &ctx.analyzer,
4467                    resolved,
4468                    candidate,
4469                )
4470            })
4471    )
4472}
4473
4474fn qualified_reference_selects_type_candidate(
4475    candidate: &CodeUnit,
4476    reference: Node<'_>,
4477    ctx: &ScanCtx<'_>,
4478) -> bool {
4479    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4480        return false;
4481    };
4482    if components.len() < 2 {
4483        return false;
4484    }
4485    let candidate_components = canonical_cpp_scope_components(candidate);
4486    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4487        match enclosing_lexical_scope_components(
4488            reference,
4489            &ctx.analyzer,
4490            ctx.visibility,
4491            ctx.file,
4492            ctx.source,
4493        ) {
4494            LexicalScopeResolution::Resolved(scope) => scope,
4495            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4496                enclosing_namespace_components(reference, ctx.source)
4497            }
4498        }
4499    });
4500    lexical_component_tiers(&components, global, &lexical_scope)
4501        .any(|qualified| qualified == candidate_components)
4502}
4503
4504fn qualified_type_component_hit_node<'tree>(
4505    component: Node<'tree>,
4506    qualified: Node<'tree>,
4507) -> Node<'tree> {
4508    let mut current = component;
4509    while let Some(parent) = current.parent() {
4510        let is_type_name = matches!(
4511            parent.kind(),
4512            "template_type"
4513                | "qualified_identifier"
4514                | "scoped_identifier"
4515                | "scoped_type_identifier"
4516        ) && parent
4517            .child_by_field_name("name")
4518            .is_some_and(|name| same_node(name, current));
4519        if !is_type_name {
4520            break;
4521        }
4522        current = parent;
4523        if same_node(parent, qualified) {
4524            break;
4525        }
4526    }
4527    current
4528}
4529
4530fn template_type_component_preserves_target(
4531    node: Node<'_>,
4532    candidates: &[CodeUnit],
4533    ctx: &ScanCtx<'_>,
4534) -> bool {
4535    template_reference_candidates_select_target(
4536        node,
4537        candidates,
4538        &ctx.analyzer,
4539        ctx.visibility,
4540        ctx.file,
4541        ctx.source,
4542        &ctx.spec.target,
4543    )
4544}
4545
4546fn template_reference_candidates_select_target(
4547    node: Node<'_>,
4548    candidates: &[CodeUnit],
4549    analyzer: &CppGraphSource<'_>,
4550    visibility: &VisibilityIndex<'_>,
4551    file: &ProjectFile,
4552    source: &str,
4553    target: &CodeUnit,
4554) -> bool {
4555    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4556        return !visibility.is_template_specialization(target);
4557    };
4558    let direct_template_name =
4559        template_reference_name_node(node).map(|name| node_text(name, source));
4560    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4561        analyzer.type_alias_provider().is_some_and(|provider| {
4562            visibility
4563                .visible_identifier_candidates(file, name)
4564                .filter(|candidate| provider.is_type_alias(candidate))
4565                .any(|candidate| {
4566                    visibility.template_alias_arguments_preserve_target(
4567                        analyzer, file, candidate, &arguments, target,
4568                    )
4569                })
4570        })
4571    });
4572    named_alias_selects_target
4573        || candidates.iter().any(|candidate| {
4574            (same_visible_symbol(candidate, target)
4575                && visibility.is_primary_template(target)
4576                && direct_template_name == Some(candidate.identifier()))
4577                || visibility.template_alias_arguments_preserve_target(
4578                    analyzer, file, candidate, &arguments, target,
4579                )
4580                || visibility
4581                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4582                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4583        })
4584}
4585
4586fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4587    let template = if node.kind() == "template_type" {
4588        node
4589    } else {
4590        node.child_by_field_name("name")
4591            .filter(|name| name.kind() == "template_type")?
4592    };
4593    template.child_by_field_name("name")
4594}
4595
4596fn type_resolution_matches_target(
4597    node: Node<'_>,
4598    unit: &CodeUnit,
4599    candidates: &[CodeUnit],
4600    ctx: &ScanCtx<'_>,
4601) -> bool {
4602    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4603}
4604
4605fn type_resolution_matches_unit_target(
4606    node: Node<'_>,
4607    unit: &CodeUnit,
4608    candidates: &[CodeUnit],
4609    target: &CodeUnit,
4610    ctx: &ScanCtx<'_>,
4611) -> bool {
4612    target_alias_candidates_visible(candidates, node, ctx)
4613        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4614}
4615
4616/// The identity half of the type-resolution match, without the alias
4617/// visibility gate.
4618///
4619/// Use it only on the without-visibility fallback path, which reports an
4620/// unproven hit. An alias spelling does not contain the target identifier, so
4621/// the name-mention fallback can never recover a rejected alias reference: the
4622/// site would disappear instead of degrading to a reviewable hit.
4623fn type_resolution_identifies_unit_target(
4624    node: Node<'_>,
4625    unit: &CodeUnit,
4626    candidates: &[CodeUnit],
4627    target: &CodeUnit,
4628    ctx: &ScanCtx<'_>,
4629) -> bool {
4630    if !template_alias_owner_matches_reference(node, target, ctx) {
4631        return false;
4632    }
4633    if ctx.visibility.is_template_specialization(target)
4634        && cpp_template_reference_arguments(node, ctx.source).is_some()
4635    {
4636        let selected_unit =
4637            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4638                ctx.visibility
4639                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4640                    .ok()
4641            });
4642        return selected_unit
4643            .as_ref()
4644            .is_some_and(|selected| same_visible_symbol(selected, target))
4645            || template_reference_candidates_select_target(
4646                node,
4647                candidates,
4648                &ctx.analyzer,
4649                ctx.visibility,
4650                ctx.file,
4651                ctx.source,
4652                target,
4653            );
4654    }
4655    unit == target
4656        || ctx
4657            .visibility
4658            .same_template_member_identity(&ctx.analyzer, unit, target)
4659        || ctx.visibility.c_tag_declaration_family_matches_target(
4660            &ctx.analyzer,
4661            ctx.file,
4662            &candidates.iter().collect::<Vec<_>>(),
4663            target,
4664        )
4665        || ctx.visibility.structured_class_alias_resolves_to_target(
4666            &ctx.analyzer,
4667            ctx.file,
4668            unit,
4669            target,
4670        )
4671        || candidates.iter().any(|candidate| {
4672            ctx.visibility
4673                .same_template_member_identity(&ctx.analyzer, candidate, target)
4674                || ctx.visibility.structured_class_alias_resolves_to_target(
4675                    &ctx.analyzer,
4676                    ctx.file,
4677                    candidate,
4678                    target,
4679                )
4680        })
4681}
4682
4683/// Keep a member alias attached to the class specialization that declares it.
4684/// A target-guided lexical lookup can otherwise retain the primary alias when
4685/// the source reference is inside a partial specialization with the same
4686/// unqualified alias name. Compare the indexed template identities instead of
4687/// rendered text or suffixes.
4688fn template_alias_owner_matches_reference(
4689    node: Node<'_>,
4690    target: &CodeUnit,
4691    ctx: &ScanCtx<'_>,
4692) -> bool {
4693    if !ctx
4694        .analyzer
4695        .type_alias_provider()
4696        .is_some_and(|provider| provider.is_type_alias(target))
4697    {
4698        return true;
4699    }
4700    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4701        return true;
4702    };
4703    if !target_owner.is_class() {
4704        return true;
4705    }
4706    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4707        return true;
4708    };
4709    if !ctx.visibility.is_template_specialization(&target_owner)
4710        && !ctx.visibility.is_template_specialization(&reference_owner)
4711    {
4712        return true;
4713    }
4714    same_visible_symbol(&target_owner, &reference_owner)
4715}
4716
4717fn inherited_injected_class_qualifier_scope<'tree>(
4718    node: Node<'tree>,
4719    ctx: &ScanCtx<'_>,
4720) -> Option<Node<'tree>> {
4721    let qualified = qualified_owner_components(node, ctx.source)?;
4722    if qualified.global || qualified.names.is_empty() {
4723        return None;
4724    }
4725    let injected_name = &qualified.names[0];
4726    if !ctx.spec.target.is_class()
4727        || ctx.spec.target.identifier() != injected_name
4728        || physically_visible_type_target(ctx).is_none()
4729    {
4730        return None;
4731    }
4732    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4733    let owner = ctx.visibility.inherited_injected_class_owner(
4734        &ctx.analyzer,
4735        ctx.file,
4736        &enclosing_owner,
4737        injected_name,
4738    )?;
4739    same_visible_symbol(&owner, &ctx.spec.target)
4740        .then(|| qualified.nodes.first().copied())
4741        .flatten()
4742}
4743
4744/// Resolve each qualified type component against the inverse target while
4745/// preserving C++ lexical-tier precedence and structured alias identity.
4746fn target_guided_qualifier_type_scopes<'tree>(
4747    node: Node<'tree>,
4748    ctx: &ScanCtx<'_>,
4749) -> Option<Vec<Node<'tree>>> {
4750    if !matches!(
4751        node.kind(),
4752        "qualified_identifier" | "scoped_type_identifier"
4753    ) {
4754        return None;
4755    }
4756    let target = physically_visible_type_target(ctx)?;
4757    let qualified = qualified_owner_components(node, ctx.source)?;
4758    if !qualified.global
4759        && qualified
4760            .names
4761            .first()
4762            .is_some_and(|name| local_receiver_alias_type_node(node, name, ctx).is_some())
4763    {
4764        return static_qualifier_type_scopes_for_components(node, qualified, ctx);
4765    }
4766    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4767    // scope before falling back to suffix recovery.  A short unqualified
4768    // owner can have a same-spelled class in a nested namespace (for example
4769    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4770    // both as possible owners and then fails closed, even though the
4771    // translation unit's lexical scope selects the global class.  Keep the
4772    // suffix path for malformed namespace sentinels, where the parser does
4773    // not expose every indexed scope component.
4774    let lexical_scope = match enclosing_lexical_scope_components(
4775        node,
4776        &ctx.analyzer,
4777        ctx.visibility,
4778        ctx.file,
4779        ctx.source,
4780    ) {
4781        LexicalScopeResolution::Resolved(scope) => scope,
4782        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4783            enclosing_namespace_components(node, ctx.source)
4784        }
4785    };
4786    let indexed_owner_scope =
4787        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4788    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4789    let mut matches = Vec::new();
4790    for component_count in 1..=qualified.names.len() {
4791        let components = &qualified.names[..component_count];
4792        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4793            .collect::<Vec<_>>();
4794        let name = components.last()?;
4795        let mut candidates = Vec::new();
4796        let mut exact_candidates = Vec::new();
4797        for candidate in ctx
4798            .visibility
4799            .visible_identifier_candidates(ctx.file, name)
4800            .filter(|candidate| candidate.is_class())
4801            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4802        {
4803            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4804                brokk_bifrost_core::analyzer::Language::Cpp,
4805                &cpp_name_for(candidate),
4806            );
4807            if !candidate_components.ends_with(components)
4808                || candidates
4809                    .iter()
4810                    .any(|existing| same_logical_symbol(existing, candidate))
4811            {
4812                continue;
4813            }
4814            let exact_lexical_scope = lexical_tiers
4815                .iter()
4816                .any(|expected| expected == &candidate_components);
4817            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4818            let structured_owner_match = indexed_owner_scope
4819                .as_ref()
4820                .is_some_and(|owner| owner.starts_with(candidate_owner))
4821                || recovered_owner_scope
4822                    .as_ref()
4823                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4824            let class_alias_owner_match = ctx
4825                .analyzer
4826                .type_alias_provider()
4827                .is_some_and(|provider| provider.is_type_alias(candidate))
4828                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4829            let macro_namespace_owner_match = is_declaration_name(node)
4830                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4831            if qualified.global && !exact_lexical_scope {
4832                continue;
4833            }
4834            if components.len() == 1
4835                && candidate_components != components
4836                && !exact_lexical_scope
4837                && !structured_owner_match
4838                && !class_alias_owner_match
4839                && !macro_namespace_owner_match
4840            {
4841                continue;
4842            }
4843            candidates.push(candidate.clone());
4844            if exact_lexical_scope {
4845                exact_candidates.push(candidate.clone());
4846            }
4847        }
4848        if !exact_candidates.is_empty() {
4849            candidates = exact_candidates;
4850        }
4851        // A typedef spelling can qualify nested C++ members while forward
4852        // lookup canonicalizes that spelling to its underlying class. Preserve
4853        // the exact alias prefix only when structured alias resolution proves
4854        // that it denotes this inverse target.
4855        let canonical_alias_target_matches = matches!(
4856            candidates.as_slice(),
4857            [candidate]
4858                if ctx
4859                    .analyzer
4860                    .type_alias_provider()
4861                    .is_some_and(|provider| provider.is_type_alias(candidate))
4862                    && type_candidate_visible_at_reference(candidate, node, ctx)
4863                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4864                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4865                        brokk_bifrost_core::analyzer::Language::Cpp,
4866                        &cpp_name_for(candidate),
4867                    ) == components
4868                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
4869        );
4870        let direct_alias_target = ctx
4871            .analyzer
4872            .type_alias_provider()
4873            .is_some_and(|provider| provider.is_type_alias(target))
4874            && candidates
4875                .iter()
4876                .any(|candidate| same_symbol(candidate, target));
4877        let unique_target = matches!(
4878            candidates.as_slice(),
4879            [candidate] if same_visible_symbol(candidate, target)
4880        );
4881        if direct_alias_target || unique_target || canonical_alias_target_matches {
4882            let matched = if ctx
4883                .analyzer
4884                .type_alias_provider()
4885                .is_some_and(|provider| provider.is_type_alias(target))
4886                && ctx
4887                    .analyzer
4888                    .parent_of(target)
4889                    .is_some_and(|owner| owner.is_class())
4890                && ctx
4891                    .visibility
4892                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4893                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4894            {
4895                // The alias declaration owns the terminal component. Keep
4896                // the inverse range narrow so `MathLib::bigint` records the
4897                // `bigint` token, not the complete qualified owner path.
4898                qualified.nodes[component_count - 1]
4899            } else {
4900                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4901            };
4902            if template_type_component_preserves_target(matched, &candidates, ctx) {
4903                matches.push(matched);
4904            }
4905        }
4906    }
4907    (!matches.is_empty()).then_some(matches)
4908}
4909
4910fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4911    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4912        return false;
4913    };
4914    ctx.visibility
4915        .visible_identifier_candidates(ctx.file, target.identifier())
4916        .any(|candidate| {
4917            alias_provider.is_type_alias(candidate)
4918                && candidate.identifier() == target.identifier()
4919                && !same_visible_symbol(candidate, target)
4920                && ctx
4921                    .analyzer
4922                    .parent_of(candidate)
4923                    .is_some_and(|owner| owner.is_class())
4924        })
4925}
4926
4927/// Recover an out-of-line owner when the owner declaration and the reference
4928/// use different unknown preprocessor guards. Keep this result unproven.
4929fn target_guided_unproven_out_of_line_owner<'tree>(
4930    node: Node<'tree>,
4931    ctx: &ScanCtx<'_>,
4932) -> Option<Node<'tree>> {
4933    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4934        || !is_declaration_name(node)
4935    {
4936        return None;
4937    }
4938    let target = physically_visible_type_target(ctx)?;
4939    if !target.is_class() {
4940        return None;
4941    }
4942    let qualified = qualified_owner_components(node, ctx.source)?;
4943    let target_components = canonical_cpp_scope_components(target);
4944    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4945    let parser_scope = enclosing_namespace_components(node, ctx.source);
4946    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4947        if !parser_scope.is_empty() || target_namespace.is_empty() {
4948            Some(parser_scope)
4949        } else {
4950            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4951        }
4952    })?;
4953    if has_malformed_wrapper_function_definition_ancestor(node)
4954        && target_namespace.starts_with(&scope)
4955        && target_namespace.len() > scope.len()
4956    {
4957        scope = target_namespace.to_vec();
4958    }
4959    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4960        .any(|components| components == target_components)
4961    {
4962        return None;
4963    }
4964    let owner_name = qualified.names.last()?;
4965    let candidates = ctx
4966        .visibility
4967        .visible_identifier_candidates(ctx.file, owner_name)
4968        .filter(|candidate| {
4969            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4970        })
4971        .collect::<Vec<_>>();
4972    if candidates.is_empty()
4973        || candidates
4974            .iter()
4975            .any(|candidate| !same_visible_symbol(candidate, target))
4976    {
4977        return None;
4978    }
4979    qualified.nodes.last().copied()
4980}
4981
4982fn macro_namespace_scope_matches(
4983    candidate_owner: &[String],
4984    node: Node<'_>,
4985    ctx: &ScanCtx<'_>,
4986) -> bool {
4987    let namespace = enclosing_namespace_components(node, ctx.source);
4988    if namespace.is_empty() || candidate_owner.is_empty() {
4989        return false;
4990    }
4991    let mut expanded_owner = Vec::new();
4992    for component in candidate_owner {
4993        if let Some(replacement) =
4994            ctx.visibility
4995                .object_macro_replacement_at(ctx.file, component, node.start_byte())
4996        {
4997            let replacement_components =
4998                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4999                    brokk_bifrost_core::analyzer::Language::Cpp,
5000                    &replacement,
5001                );
5002            if replacement_components.is_empty() {
5003                return false;
5004            }
5005            expanded_owner.extend(replacement_components);
5006        } else {
5007            expanded_owner.push(component.clone());
5008        }
5009    }
5010    expanded_owner == namespace
5011}
5012
5013fn target_guided_missing_type_leaf<'tree>(
5014    node: Node<'tree>,
5015    ctx: &ScanCtx<'_>,
5016) -> Option<Node<'tree>> {
5017    physically_visible_type_target(ctx)?;
5018    target_guided_missing_dependent_nested_type_leaf(node, ctx)
5019        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
5020        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
5021        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
5022        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
5023        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
5024}
5025
5026/// Recover a bare class-owned alias whose structured canonical target is the
5027/// requested type. The class owner must enclose the reference, and every alias
5028/// with that spelling in the owner chain must preserve the same target.
5029fn target_guided_missing_class_alias_target_type_leaf<'tree>(
5030    node: Node<'tree>,
5031    ctx: &ScanCtx<'_>,
5032) -> Option<Node<'tree>> {
5033    if node.kind() != "type_identifier"
5034        || is_declaration_name(node)
5035        || local_type_name_shadows(node, ctx)
5036    {
5037        return None;
5038    }
5039    let alias_provider = ctx.analyzer.type_alias_provider()?;
5040    let name = node_text(node, ctx.source);
5041    let aliases = ctx
5042        .visibility
5043        .visible_identifier_candidates(ctx.file, name)
5044        .filter(|candidate| alias_provider.is_type_alias(candidate))
5045        .filter(|candidate| {
5046            ctx.analyzer
5047                .parent_of(candidate)
5048                .is_some_and(|owner| owner.is_class())
5049        })
5050        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5051        .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5052        .collect::<Vec<_>>();
5053    (!aliases.is_empty()
5054        && aliases.iter().all(|candidate| {
5055            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5056        }))
5057    .then_some(node)
5058}
5059
5060/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5061/// type only when the indexed class owner proves the alias declaration. This
5062/// narrow path covers malformed class bodies without accepting unrelated aliases.
5063fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5064    node: Node<'tree>,
5065    ctx: &ScanCtx<'_>,
5066) -> Option<Node<'tree>> {
5067    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5068        matches!(
5069            declaration.kind(),
5070            "parameter_declaration" | "optional_parameter_declaration"
5071        )
5072    });
5073    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5074        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5075    });
5076    if !parameter && !placement_new_type {
5077        return None;
5078    }
5079    if !ctx
5080        .analyzer
5081        .type_alias_provider()
5082        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5083        || !type_alias_owner_matches_structured_reference(node, ctx)
5084    {
5085        return None;
5086    }
5087    target_guided_missing_declaration_type_leaf(node, ctx)
5088}
5089
5090/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5091/// sentinel prevents ordinary lexical resolution. The indexed target must have
5092/// an indexed class parent, the structured owner path must compose with one
5093/// proven lexical namespace source, and every visible candidate at that exact
5094/// owner path must be the indexed parent. This keeps the fallback owner-based;
5095/// a same-spelled nested type under another template remains unproven.
5096fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5097    node: Node<'tree>,
5098    ctx: &ScanCtx<'_>,
5099) -> Option<Node<'tree>> {
5100    if !matches!(
5101        node.kind(),
5102        "qualified_identifier" | "scoped_type_identifier"
5103    ) || !qualified_type_scope_contains_template(node)
5104    {
5105        return None;
5106    }
5107    let name = node
5108        .child_by_field_name("name")
5109        .filter(|name| name.kind() == "type_identifier")?;
5110    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5111        return None;
5112    }
5113    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5114    if !owner_target.is_class() {
5115        return None;
5116    }
5117    let owner = node.child_by_field_name("scope")?;
5118    let owner_resolution = resolve_type_node_lexically_for_target(
5119        owner,
5120        &ctx.analyzer,
5121        ctx.visibility,
5122        &ctx.ordinary_type_imports,
5123        ctx.file,
5124        ctx.source,
5125        &owner_target,
5126        Some(&ctx.lexical_scope_cache),
5127        ctx.recovered_sentinel_scope(owner).as_deref(),
5128    );
5129    if matches!(
5130        owner_resolution,
5131        LexicalTypeResolution::Resolved {
5132            ref unit,
5133            ref candidates,
5134            ..
5135        } if type_resolution_matches_unit_target(
5136            owner,
5137            unit,
5138            candidates,
5139            &owner_target,
5140            ctx,
5141        )
5142    ) {
5143        return Some(name);
5144    }
5145
5146    let qualified = qualified_owner_components(node, ctx.source)?;
5147    let namespace = ctx
5148        .lexical_scope_cache
5149        .orphaned
5150        .enclosing_namespace_components(node, ctx.source);
5151    let indexed_scope = ctx
5152        .recovered_sentinel_scope(node)
5153        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5154        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5155    let owner_components = canonical_cpp_scope_components(&owner_target);
5156    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5157        .any(|components| components == owner_components)
5158    {
5159        return None;
5160    }
5161    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5162        .into_iter()
5163        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5164        .collect::<Vec<_>>();
5165    (!scoped_candidates.is_empty()
5166        && scoped_candidates
5167            .iter()
5168            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5169    .then_some(name)
5170}
5171
5172/// Recover a nested type-alias reference when parser recovery leaves an
5173/// unqualified template argument under a member function.  The ordinary
5174/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5175/// even though the indexed callable owner proves that the reference is inside
5176/// the class which declares the target alias.
5177fn target_guided_missing_member_alias_type_leaf<'tree>(
5178    node: Node<'tree>,
5179    ctx: &ScanCtx<'_>,
5180) -> Option<Node<'tree>> {
5181    if !is_cpp_template_argument_type_leaf(node)
5182        || is_declaration_name(node)
5183        || ctx
5184            .target_declaration_ranges
5185            .iter()
5186            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5187        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5188        || local_type_name_shadows(node, ctx)
5189        || !ctx
5190            .analyzer
5191            .type_alias_provider()
5192            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5193    {
5194        return None;
5195    }
5196    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5197    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5198    if !owner_scope_matches
5199        && !indexed_scope.is_some_and(|scope| {
5200            indexed_scope_matches_target_name(
5201                &scope,
5202                &[ctx.spec.target.identifier().to_string()],
5203                false,
5204                &ctx.spec.target,
5205            )
5206        })
5207    {
5208        return None;
5209    }
5210    // The indexed symbol table intentionally retains declarations from every
5211    // preprocessor branch and from later source positions.  The recovered
5212    // class-owner scope proves the spelling, but it does not prove that this
5213    // alias was active and introduced before the reference.  Apply the same
5214    // structured guard/source-order check used by the ordinary resolver before
5215    // turning the target-guided recovery into a proven hit.
5216    if !(ctx.visibility.external_type_candidate_visible_in_context(
5217        &ctx.analyzer,
5218        ctx.file,
5219        &ctx.spec.target,
5220        node,
5221    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5222    {
5223        return None;
5224    }
5225    let target_visible = ctx
5226        .visibility
5227        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5228        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5229    target_visible.then_some(node)
5230}
5231
5232/// Recover a class/enum template argument only when the parser's ordinary
5233/// lexical lookup failed but the indexed scope and visible declaration still
5234/// prove the exact target. This intentionally excludes aliases: an alias
5235/// argument needs its own template-argument selection path, while a direct
5236/// class/enum argument can be identified by its canonical scope and symbol.
5237fn target_guided_missing_template_argument_type_leaf<'tree>(
5238    node: Node<'tree>,
5239    ctx: &ScanCtx<'_>,
5240) -> Option<Node<'tree>> {
5241    let target = &ctx.spec.target;
5242    let name = node_text(node, ctx.source);
5243    if !target.is_class()
5244        || !is_cpp_template_argument_type_leaf(node)
5245        || is_declaration_name(node)
5246        || name != target.identifier()
5247        || ctx.local_shadows.is_shadowed(name)
5248        || local_type_name_shadows(node, ctx)
5249        || !ctx.visibility.is_physically_visible(ctx.file, target)
5250        || ctx
5251            .analyzer
5252            .type_alias_provider()
5253            .is_some_and(|provider| provider.is_type_alias(target))
5254    {
5255        return None;
5256    }
5257
5258    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5259    let target_components = canonical_cpp_scope_components(target);
5260    if target_components.last().map(String::as_str) != Some(name)
5261        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5262            .any(|components| components == target_components)
5263    {
5264        return None;
5265    }
5266
5267    // The direct visible class candidate supplies the declaration identity;
5268    // the scope check above supplies its canonical owner path. Do not let an
5269    // alias or a same-scoped competing class enter this recovery path.
5270    let candidates = visible_type_identifier_candidates(ctx, name);
5271    if candidates.is_empty()
5272        || candidates.iter().any(|candidate| {
5273            !candidate.is_class()
5274                || ctx
5275                    .analyzer
5276                    .type_alias_provider()
5277                    .is_some_and(|provider| provider.is_type_alias(candidate))
5278                || (!same_visible_symbol(candidate, target)
5279                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5280                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5281        })
5282        || !candidates
5283            .iter()
5284            .any(|candidate| same_visible_symbol(candidate, target))
5285    {
5286        return None;
5287    }
5288
5289    // Physical visibility covers the file/import projection; this second
5290    // guard preserves declaration ordering and preprocessor branch identity.
5291    ctx.visibility
5292        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5293        .then_some(node)
5294}
5295
5296/// Recover the class owner of an out-of-line member whose trailing attribute
5297/// macro was parsed as a separate function definition around the real body.
5298fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5299    let mut function = node;
5300    while function.kind() != "function_definition" {
5301        function = ctx.ancestry.parent(function)?;
5302    }
5303    let macro_name = function_definition_name_node(function)?;
5304    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5305        return None;
5306    }
5307
5308    // An unknown trailing attribute macro can split one real definition into
5309    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5310    // macro-named function definition that owns the body. Recover only that
5311    // exact CST sequence; a complete declaration or a non-macro function is
5312    // an ordinary independent construct.
5313    let declaration = function.prev_named_sibling()?;
5314    if declaration.kind() != "declaration"
5315        || !declaration.has_error()
5316        || function.start_position().row > declaration.end_position().row + 1
5317    {
5318        return None;
5319    }
5320    let mut missing_semicolon = false;
5321    let mut real_semicolon = false;
5322    for child in children_iter(declaration) {
5323        if child.kind() == ";" {
5324            missing_semicolon |= child.is_missing();
5325            real_semicolon |= !child.is_missing();
5326        }
5327    }
5328    if !missing_semicolon || real_semicolon {
5329        return None;
5330    }
5331
5332    let initializer = declaration.child_by_field_name("declarator")?;
5333    if initializer.kind() != "init_declarator"
5334        || initializer
5335            .child_by_field_name("value")
5336            .is_none_or(|value| value.kind() != "argument_list")
5337    {
5338        return None;
5339    }
5340    let qualified_name = initializer
5341        .child_by_field_name("declarator")
5342        .and_then(declarator_name_node)?;
5343    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5344    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5345    match ctx.visibility.resolve_type_components_lexically(
5346        &ctx.analyzer,
5347        ctx.file,
5348        &qualified.names,
5349        qualified.global,
5350        &lexical_scope,
5351    ) {
5352        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5353        LexicalTypeResolution::Resolved { .. }
5354        | LexicalTypeResolution::Ambiguous
5355        | LexicalTypeResolution::Missing => None,
5356    }
5357}
5358
5359/// A class member alias is visible throughout its complete class scope, even
5360/// when its declaration byte follows a recovered out-of-line member's
5361/// trailing return type. Match the indexed owner path structurally before
5362/// allowing the guard-only visibility check above to waive source ordering.
5363fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5364    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5365}
5366
5367fn member_alias_owner_matches_reference_for(
5368    target: &CodeUnit,
5369    node: Node<'_>,
5370    ctx: &ScanCtx<'_>,
5371) -> bool {
5372    let Some(owner) = ctx.analyzer.parent_of(target) else {
5373        return false;
5374    };
5375    if !owner.is_class() {
5376        return false;
5377    }
5378    let reference_owner = ctx
5379        .class_ranges
5380        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5381        .or_else(|| structured_enclosing_owner(node, ctx));
5382    if reference_owner.as_ref().is_some_and(|reference_owner| {
5383        ctx.visibility
5384            .same_template_owner_identity(&owner, reference_owner)
5385    }) {
5386        return true;
5387    }
5388    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5389        ctx.visibility
5390            .same_template_owner_identity(&owner, &reference_owner)
5391    }) {
5392        return true;
5393    }
5394    if reference_owner.is_some_and(|reference_owner| {
5395        matches!(
5396            resolve_declaring_member_owner(
5397                &ctx.analyzer,
5398                ctx.visibility,
5399                ctx.file,
5400                &reference_owner,
5401                target.identifier(),
5402            ),
5403            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5404                if ctx
5405                    .visibility
5406                    .same_template_owner_identity(&owner, &declaring_owner)
5407        )
5408    }) {
5409        return true;
5410    }
5411    let range = Range {
5412        start_byte: node.start_byte(),
5413        end_byte: node.end_byte(),
5414        start_line: node.start_position().row + 1,
5415        end_line: node.end_position().row + 1,
5416    };
5417    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5418    while let Some(candidate) = indexed_enclosing {
5419        if candidate.is_class()
5420            && ctx
5421                .visibility
5422                .same_template_owner_identity(&owner, &candidate)
5423        {
5424            return true;
5425        }
5426        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5427    }
5428    if let Some(reference_body) = malformed_recovered_class_body(node) {
5429        let mut root = node;
5430        while let Some(parent) = ctx.ancestry.parent(root) {
5431            root = parent;
5432        }
5433        if ctx.analyzer.ranges(target).iter().any(|range| {
5434            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5435                .and_then(malformed_recovered_class_body)
5436                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5437        }) {
5438            return true;
5439        }
5440    }
5441    if structured_enclosing_owner(node, ctx)
5442        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5443    {
5444        return true;
5445    }
5446    let owner_components = canonical_cpp_scope_components(&owner);
5447    if ctx
5448        .recovered_sentinel_scope(node)
5449        .is_some_and(|scope| scope == owner_components)
5450    {
5451        return true;
5452    }
5453    if matches!(
5454        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5455            node,
5456            &ctx.analyzer,
5457            ctx.visibility,
5458            ctx.file,
5459            ctx.source,
5460            false,
5461            false,
5462            Some(&ctx.lexical_scope_cache),
5463        ),
5464        LexicalScopeResolution::Resolved(reference_scope)
5465            if reference_scope == owner_components
5466    ) {
5467        return true;
5468    }
5469    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5470    else {
5471        return false;
5472    };
5473    !owner_components.is_empty() && reference_scope == owner_components
5474}
5475
5476fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5477    loop {
5478        if node.kind() == "compound_statement"
5479            && node
5480                .parent()
5481                .is_some_and(|parent| parent.kind() == "declaration_list")
5482            && node.prev_named_sibling().is_some_and(|header| {
5483                header.kind() == "ERROR"
5484                    && header.end_byte() <= node.start_byte()
5485                    && error_contains_class_header(header)
5486            })
5487        {
5488            return Some(node);
5489        }
5490        node = node.parent()?;
5491    }
5492}
5493
5494/// Whether a shallow parser error contains a recognizable class header.
5495pub fn error_contains_class_header(node: Node<'_>) -> bool {
5496    let mut pending = vec![(node, 0usize)];
5497    while let Some((current, depth)) = pending.pop() {
5498        if matches!(current.kind(), "class" | "struct" | "union") {
5499            let mut sibling = current.next_sibling();
5500            let mut saw_name = false;
5501            while let Some(candidate) = sibling {
5502                match candidate.kind() {
5503                    "comment" => {}
5504                    "{" | "base_class_clause" | ":" => return saw_name,
5505                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5506                    _ if !candidate.is_named() => {}
5507                    _ => break,
5508                }
5509                sibling = candidate.next_sibling();
5510            }
5511        }
5512        if depth >= 1 {
5513            continue;
5514        }
5515        let mut cursor = current.walk();
5516        pending.extend(
5517            current
5518                .children(&mut cursor)
5519                .filter(|child| child.kind() != "compound_statement")
5520                .map(|child| (child, depth + 1)),
5521        );
5522    }
5523    false
5524}
5525
5526fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5527    has_ancestor_kind(node, "compound_statement")
5528        && ctx
5529            .visibility
5530            .external_type_candidate_guard_compatible_in_context(
5531                &ctx.analyzer,
5532                ctx.file,
5533                &ctx.spec.target,
5534                node,
5535            )
5536}
5537
5538fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5539    ctx.analyzer
5540        .type_alias_provider()
5541        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5542        && member_alias_owner_matches_reference(node, ctx)
5543}
5544
5545/// A nested class can use aliases declared by any enclosing class. Preserve
5546/// that structured owner chain for malformed macro-return nodes, whose phantom
5547/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5548fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5549    if !ctx
5550        .analyzer
5551        .type_alias_provider()
5552        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5553    {
5554        return false;
5555    }
5556    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5557        return false;
5558    };
5559    let mut reference_owner = structured_enclosing_owner(node, ctx);
5560    while let Some(owner) = reference_owner {
5561        if same_logical_symbol(&target_owner, &owner) {
5562            return true;
5563        }
5564        reference_owner = ctx.analyzer.parent_of(&owner);
5565    }
5566    false
5567}
5568
5569/// An enclosing class alias is only usable when no nearer class declares the
5570/// same type name. The recovered macro-return path does not have a complete
5571/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5572/// rule before the enclosing alias fast path runs.
5573fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5574    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5575        return false;
5576    };
5577    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5578        return false;
5579    };
5580    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5581        return false;
5582    };
5583    let candidates = ctx
5584        .visibility
5585        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5586        .filter(|candidate| {
5587            candidate.is_class()
5588                && alias_provider.is_type_alias(candidate)
5589                && !same_visible_symbol(candidate, &ctx.spec.target)
5590        })
5591        .cloned()
5592        .collect::<Vec<_>>();
5593
5594    let mut owner = Some(reference_owner);
5595    while let Some(owner_unit) = owner {
5596        if same_logical_symbol(&target_owner, &owner_unit) {
5597            return false;
5598        }
5599        if candidates.iter().any(|candidate| {
5600            ctx.analyzer
5601                .parent_of(candidate)
5602                .is_some_and(|candidate_owner| {
5603                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5604                })
5605                && ctx
5606                    .visibility
5607                    .external_type_candidate_guard_compatible_in_context(
5608                        &ctx.analyzer,
5609                        ctx.file,
5610                        candidate,
5611                        node,
5612                    )
5613        }) {
5614            return true;
5615        }
5616        owner = ctx.analyzer.parent_of(&owner_unit);
5617    }
5618    false
5619}
5620
5621fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5622    if cpp_active_template_type_parameter(
5623        node,
5624        ctx.spec.target.identifier(),
5625        ctx.source,
5626        &ctx.ancestry,
5627    ) {
5628        return true;
5629    }
5630    let Some(callable) = nearest_callable_scope(node) else {
5631        return false;
5632    };
5633    let mut root_callable = callable;
5634    let mut ancestor = ctx.ancestry.parent(callable);
5635    while let Some(current) = ancestor {
5636        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5637            root_callable = current;
5638        }
5639        ancestor = ctx.ancestry.parent(current);
5640    }
5641
5642    let mut stack = vec![root_callable];
5643    while let Some(current) = stack.pop() {
5644        if current.start_byte() >= node.start_byte() {
5645            continue;
5646        }
5647        if let Some(name) = local_type_name_declaration_node(current)
5648            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5649            && nearest_callable_scope(current).is_some_and(|owner| {
5650                !is_malformed_wrapper_function_definition(owner)
5651                    && owner.start_byte() <= callable.start_byte()
5652                    && callable.end_byte() <= owner.end_byte()
5653            })
5654            && local_alias_scope_contains_node(current, node)
5655        {
5656            return true;
5657        }
5658        let mut cursor = current.walk();
5659        stack.extend(current.named_children(&mut cursor));
5660    }
5661    false
5662}
5663
5664fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5665    local_type_alias_name_node(node).or_else(|| match node.kind() {
5666        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5667            .child_by_field_name("name")
5668            .filter(|name| is_declaration_name(*name)),
5669        _ => None,
5670    })
5671}
5672
5673fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5674    loop {
5675        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5676            return Some(node);
5677        }
5678        node = node.parent()?;
5679    }
5680}
5681
5682fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5683    match node.kind() {
5684        "alias_declaration" => node.child_by_field_name("name"),
5685        "type_definition" => node
5686            .child_by_field_name("declarator")
5687            .and_then(declarator_name_node),
5688        _ => None,
5689    }
5690}
5691
5692fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5693    let mut current = alias.parent();
5694    while let Some(parent) = current {
5695        if matches!(
5696            parent.kind(),
5697            "class_specifier" | "struct_specifier" | "union_specifier"
5698        ) {
5699            return false;
5700        }
5701        if parent.kind() == "compound_statement" {
5702            return parent.start_byte() <= node.start_byte()
5703                && node.end_byte() <= parent.end_byte();
5704        }
5705        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5706            let Some(body) = parent.child_by_field_name("body") else {
5707                return false;
5708            };
5709            return node_is_within(body, alias) && node_is_within(body, node);
5710        }
5711        current = parent.parent();
5712    }
5713    false
5714}
5715
5716fn target_guided_missing_declaration_type_leaf<'tree>(
5717    node: Node<'tree>,
5718    ctx: &ScanCtx<'_>,
5719) -> Option<Node<'tree>> {
5720    if is_declaration_name(node) {
5721        return None;
5722    }
5723    let component_nodes = cpp_name_component_nodes(node)?;
5724    let name_node = component_nodes.last().copied()?;
5725    let name = node_text(name_node, ctx.source);
5726    if name != ctx.spec.target.identifier() {
5727        return None;
5728    }
5729    let inside_target_declaration = ctx
5730        .target_declaration_ranges
5731        .iter()
5732        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5733    if !inside_target_declaration
5734        && !ctx.visibility.external_type_candidate_visible_in_context(
5735            &ctx.analyzer,
5736            ctx.file,
5737            &ctx.spec.target,
5738            node,
5739        )
5740    {
5741        return None;
5742    }
5743    let components = component_nodes
5744        .iter()
5745        .map(|component| node_text(*component, ctx.source).to_string())
5746        .collect::<Vec<_>>();
5747    let local_alias_shadow = local_type_name_shadows(node, ctx);
5748    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5749    let indexed_alias_owner = ctx
5750        .analyzer
5751        .type_alias_provider()
5752        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5753        && member_alias_owner_matches_reference(node, ctx);
5754    let target_alias_self_reference = inside_target_declaration
5755        && ctx
5756            .analyzer
5757            .type_alias_provider()
5758            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5759    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5760        &ctx.analyzer,
5761        ctx.file,
5762        &ctx.spec.target,
5763        node,
5764    ) || member_alias_complete_class_context(node, ctx);
5765    if !target_alias_self_reference
5766        && !local_alias_shadow
5767        && member_alias_visible
5768        && (structured_alias_owner || indexed_alias_owner)
5769    {
5770        return Some(node);
5771    }
5772    let declaration = nearest_declaration_type_context(node)?;
5773    let candidates = visible_type_identifier_candidates(ctx, name);
5774    let unique_visible_target = !candidates.is_empty()
5775        && candidates
5776            .iter()
5777            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5778    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5779    let exact_scope_match = indexed_scope_matches_target_name(
5780        &indexed_scope,
5781        &components,
5782        is_globally_qualified_cpp_name(node),
5783        &ctx.spec.target,
5784    );
5785    if matches!(declaration.kind(), "field_declaration" | "declaration") {
5786        let parser_lost_declaration_scope =
5787            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5788                && unique_visible_target;
5789        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5790    }
5791    let lost_namespace_parameter_context =
5792        matches!(
5793            declaration.kind(),
5794            "parameter_declaration" | "optional_parameter_declaration"
5795        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5796    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5797        return Some(node);
5798    }
5799    None
5800}
5801
5802fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5803    node: Node<'tree>,
5804    ctx: &ScanCtx<'_>,
5805) -> Option<Node<'tree>> {
5806    let mut stack = vec![node];
5807    while let Some(candidate) = stack.pop() {
5808        if candidate.kind() == "type_identifier"
5809            && !is_declaration_name(candidate)
5810            && matches!(
5811                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
5812                Some("template_type")
5813            )
5814        {
5815            let mut current = ctx.ancestry.parent(candidate);
5816            let mut saw_qualified = false;
5817            let mut saw_dependent = false;
5818            let mut saw_type_descriptor = false;
5819            let mut saw_alias_declaration = false;
5820            while let Some(ancestor) = current {
5821                match ancestor.kind() {
5822                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5823                    "dependent_type" => saw_dependent = true,
5824                    "type_descriptor" => saw_type_descriptor = true,
5825                    "alias_declaration" => {
5826                        saw_alias_declaration = true;
5827                        break;
5828                    }
5829                    "template_type"
5830                    | "template_argument_list"
5831                    | "typename"
5832                    | "template_declaration" => {}
5833                    _ => {}
5834                }
5835                current = ctx.ancestry.parent(ancestor);
5836            }
5837            let name = node_text(candidate, ctx.source);
5838            let visible_candidates = visible_type_identifier_candidates(ctx, name);
5839            let canonical_alias_target = visible_candidates
5840                .iter()
5841                .filter_map(|alias| ctx.visibility.alias_target(alias))
5842                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5843            let alias_resolves =
5844                ctx.visibility
5845                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
5846                    || canonical_alias_target;
5847            if saw_qualified
5848                && saw_dependent
5849                && saw_type_descriptor
5850                && saw_alias_declaration
5851                && alias_resolves
5852                && ctx.visibility.external_type_candidate_visible_in_context(
5853                    &ctx.analyzer,
5854                    ctx.file,
5855                    &ctx.spec.target,
5856                    candidate,
5857                )
5858            {
5859                return Some(candidate);
5860            }
5861        }
5862        push_named_children_reversed(candidate, &mut stack);
5863    }
5864    None
5865}
5866
5867fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5868    let mut current = Some(node);
5869    while let Some(ancestor) = current {
5870        if matches!(
5871            ancestor.kind(),
5872            "field_declaration"
5873                | "parameter_declaration"
5874                | "optional_parameter_declaration"
5875                | "declaration"
5876                | "type_descriptor"
5877        ) {
5878            let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
5879            if contains_type
5880                && !(ancestor.kind() == "type_descriptor"
5881                    && is_cpp_template_argument_type_leaf(node))
5882            {
5883                return Some(ancestor);
5884            }
5885            if ancestor.kind() == "type_descriptor"
5886                && ancestor.parent().is_some_and(|parent| {
5887                    matches!(
5888                        parent.kind(),
5889                        "cast_expression"
5890                            | "new_expression"
5891                            | "sizeof_expression"
5892                            | "alignof_expression"
5893                            | "typeid_expression"
5894                    )
5895                })
5896            {
5897                return Some(ancestor);
5898            }
5899        }
5900        if matches!(
5901            ancestor.kind(),
5902            "compound_statement"
5903                | "translation_unit"
5904                | "namespace_definition"
5905                | "alias_declaration"
5906                | "type_definition"
5907                | "base_class_clause"
5908        ) {
5909            return None;
5910        }
5911        current = ancestor.parent();
5912    }
5913    None
5914}
5915
5916fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5917    let mut candidates = Vec::new();
5918    for candidate in ctx
5919        .visibility
5920        .visible_identifier_candidates(ctx.file, name)
5921        .filter(|candidate| {
5922            candidate.is_class()
5923                || ctx
5924                    .analyzer
5925                    .type_alias_provider()
5926                    .is_some_and(|provider| provider.is_type_alias(candidate))
5927        })
5928    {
5929        if !candidates
5930            .iter()
5931            .any(|existing| same_logical_symbol(existing, candidate))
5932        {
5933            candidates.push(candidate.clone());
5934        }
5935    }
5936    candidates
5937}
5938
5939/// Recover a direct type-alias argument of `static_cast` when parser recovery
5940/// misclassifies a namespace alias as a local declaration. The indexed scope
5941/// and exact alias identity are required so a same-spelled alias in another
5942/// namespace remains excluded.
5943fn target_guided_static_cast_alias_type_descriptor<'tree>(
5944    node: Node<'tree>,
5945    ctx: &ScanCtx<'_>,
5946) -> Option<Node<'tree>> {
5947    if node.kind() != "type_descriptor" {
5948        return None;
5949    }
5950    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
5951        parent.kind() == "template_argument_list"
5952            && parent.named_child_count() == 1
5953            && parent.named_child(0) == Some(node)
5954    })?;
5955    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
5956        parent.kind() == "template_function"
5957            && parent.child_by_field_name("arguments") == Some(argument_list)
5958    })?;
5959    let name = template.child_by_field_name("name")?;
5960    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5961        return None;
5962    }
5963    let target = &ctx.spec.target;
5964    if node_text(node, ctx.source) != target.identifier()
5965        || !ctx
5966            .analyzer
5967            .type_alias_provider()
5968            .is_some_and(|provider| provider.is_type_alias(target))
5969        || !ctx.visibility.is_physically_visible(ctx.file, target)
5970        || !ctx.visibility.external_type_candidate_visible_in_context(
5971            &ctx.analyzer,
5972            ctx.file,
5973            target,
5974            node,
5975        )
5976    {
5977        return None;
5978    }
5979
5980    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5981    let target_scope = canonical_cpp_scope_components(target);
5982    let name_components = [target.identifier().to_string()];
5983    if !lexical_component_tiers(&name_components, false, &indexed_scope)
5984        .any(|components| components == target_scope)
5985    {
5986        return None;
5987    }
5988
5989    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5990    if !candidates
5991        .iter()
5992        .any(|candidate| same_visible_symbol(candidate, target))
5993    {
5994        return None;
5995    }
5996    if candidates.iter().any(|candidate| {
5997        !same_visible_symbol(candidate, target)
5998            && lexical_component_tiers(&name_components, false, &indexed_scope)
5999                .any(|components| components == canonical_cpp_scope_components(candidate))
6000    }) {
6001        return None;
6002    }
6003    Some(node)
6004}
6005
6006fn indexed_scope_matches_target_name(
6007    indexed_scope: &[String],
6008    components: &[String],
6009    global: bool,
6010    target: &CodeUnit,
6011) -> bool {
6012    let target_name = cpp_name_for(target);
6013    lexical_component_tiers(components, global, indexed_scope)
6014        .any(|qualified| qualified.join("::") == target_name)
6015}
6016
6017fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
6018    // A global scope or one surviving name alone cannot distinguish a lost
6019    // namespace from a real global/unrelated reference. Require an enclosing
6020    // declaration component as well as a namespace component matching the
6021    // target's qualified scope.
6022    if target.package_name().is_empty() || indexed_scope.len() <= 1 {
6023        return false;
6024    }
6025    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6026        brokk_bifrost_core::analyzer::Language::Cpp,
6027        &cpp_name_for(target),
6028    );
6029    target_scope.pop();
6030    (1..indexed_scope.len())
6031        .rev()
6032        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
6033}
6034
6035fn indexed_enclosing_lexical_scope(
6036    analyzer: &CppGraphSource<'_>,
6037    file: &ProjectFile,
6038    node: Node<'_>,
6039) -> Option<Vec<String>> {
6040    let range = Range {
6041        start_byte: node.start_byte(),
6042        end_byte: node.end_byte(),
6043        start_line: node.start_position().row,
6044        end_line: node.end_position().row,
6045    };
6046    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6047    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6048        brokk_bifrost_core::analyzer::Language::Cpp,
6049        &cpp_name_for(&enclosing),
6050    );
6051    if !enclosing.is_class() && !enclosing.is_module() {
6052        components.pop();
6053    }
6054    Some(components)
6055}
6056
6057fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6058    if node.kind() != "qualified_identifier" {
6059        return None;
6060    }
6061    let mut stack = vec![node];
6062    while let Some(current) = stack.pop() {
6063        if current.kind() != "qualified_identifier" {
6064            continue;
6065        }
6066        if let Some(scope) = current.child_by_field_name("scope") {
6067            let text = qualified_scope_text(scope, ctx.source);
6068            if name_mentions(&text, &ctx.spec.member_name) {
6069                return Some(scope);
6070            }
6071        }
6072        let mut cursor = current.walk();
6073        for child in current.named_children(&mut cursor) {
6074            if child.kind() == "qualified_identifier" {
6075                stack.push(child);
6076            }
6077        }
6078    }
6079    None
6080}
6081
6082fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6083    let mut parts = vec![node_text(scope, source).to_string()];
6084    let mut current = scope.parent();
6085    while let Some(qualified) = current {
6086        let Some(parent) = qualified.parent() else {
6087            break;
6088        };
6089        if parent.kind() != "qualified_identifier"
6090            || parent.child_by_field_name("name") != Some(qualified)
6091        {
6092            break;
6093        }
6094        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6095            parts.push(node_text(outer_scope, source).to_string());
6096        }
6097        current = Some(parent);
6098    }
6099    parts.reverse();
6100    parts.join("::")
6101}
6102
6103fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6104    if node.kind() == "using_declaration" {
6105        maybe_record_using_callable_hit(node, ctx);
6106        return;
6107    }
6108    if has_ancestor_kind(node, "using_declaration") {
6109        return;
6110    }
6111    if node.kind() == "function_definition" {
6112        return;
6113    }
6114    if !matches!(
6115        node.kind(),
6116        "call_expression"
6117            | "new_expression"
6118            | "compound_literal_expression"
6119            | "declaration"
6120            | "field_initializer"
6121    ) {
6122        return;
6123    }
6124    let Some(owner) = ctx.spec.owner.as_ref() else {
6125        return;
6126    };
6127    if node.kind() == "field_initializer" {
6128        if !field_initializer_constructs_target(node, ctx, owner)
6129            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6130        {
6131            return;
6132        }
6133        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6134            match ctx
6135                .visibility
6136                .call_arity_evidence(ctx.file, node, ctx.source)
6137                .accepts(expected)
6138            {
6139                Some(true) => {}
6140                Some(false) => return,
6141                None => {
6142                    push_unproven_hit(node, ctx);
6143                    return;
6144                }
6145            }
6146        }
6147        match constructor_overload_selection(node, ctx) {
6148            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6149            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6150            ConstructorOverloadSelection::OtherOverload => {}
6151        }
6152        return;
6153    }
6154    if node.kind() == "declaration" {
6155        if declaration_is_object_construction_candidate(node, ctx)
6156            && declaration_mentions_type(node, ctx, owner)
6157            && ctx
6158                .spec
6159                .callable_arity_at(node.start_byte())
6160                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6161        {
6162            match constructor_overload_selection(node, ctx) {
6163                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6164                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6165                ConstructorOverloadSelection::OtherOverload => {}
6166            }
6167        }
6168        return;
6169    }
6170    let Some(type_node) = constructor_type_node(node) else {
6171        return;
6172    };
6173    let hit_node = function_terminal_node(type_node);
6174    let text = node_text(type_node, ctx.source);
6175    if !name_mentions(text, &ctx.spec.member_name) {
6176        return;
6177    }
6178    *ctx.raw_match_count += 1;
6179    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6180        match ctx
6181            .visibility
6182            .call_arity_evidence(ctx.file, node, ctx.source)
6183            .accepts(expected)
6184        {
6185            Some(true) => {}
6186            Some(false) => return,
6187            None => {
6188                push_unproven_hit(hit_node, ctx);
6189                return;
6190            }
6191        }
6192    }
6193    match constructor_overload_selection(node, ctx) {
6194        ConstructorOverloadSelection::Target => {}
6195        ConstructorOverloadSelection::Ambiguous => {
6196            push_unproven_hit(hit_node, ctx);
6197            return;
6198        }
6199        ConstructorOverloadSelection::OtherOverload => return,
6200    }
6201    let structured_resolution = resolve_type_node_lexically_for_target(
6202        type_node,
6203        &ctx.analyzer,
6204        ctx.visibility,
6205        &ctx.ordinary_type_imports,
6206        ctx.file,
6207        ctx.source,
6208        owner,
6209        Some(&ctx.lexical_scope_cache),
6210        ctx.recovered_sentinel_scope(type_node).as_deref(),
6211    );
6212    let structurally_resolves = matches!(
6213        &structured_resolution,
6214        LexicalTypeResolution::Resolved {
6215            unit, candidates, ..
6216        } if same_visible_symbol(unit, owner)
6217            || candidates
6218                .iter()
6219                .any(|candidate| same_visible_symbol(candidate, owner))
6220    );
6221    if structurally_resolves
6222        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6223            && ctx
6224                .visibility
6225                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6226    {
6227        push_hit(hit_node, ctx);
6228    } else {
6229        push_unproven_hit(hit_node, ctx);
6230    }
6231}
6232
6233/// What the overload filter says about a construction site relative to the
6234/// constructor being scanned.
6235enum ConstructorOverloadSelection {
6236    /// The arguments leave the scan target as the only viable constructor, or
6237    /// the filter has no evidence to apply here. Either way nothing about the
6238    /// arguments argues against the site, so the rest of the scan decides it.
6239    Target,
6240    /// More than one of the owner's constructors stays viable, so the site
6241    /// cannot be proven to belong to the target.
6242    Ambiguous,
6243    /// The arguments select a sibling constructor and not the target.
6244    OtherOverload,
6245}
6246
6247/// Which of the owner's constructors the arguments at a construction site
6248/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6249/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6250/// `T var(args)` declaration.
6251///
6252/// This is the inverse reading of the forward direction's overload choice. The
6253/// candidate set is the owner's arity-compatible constructors, the arguments are
6254/// typed by the same `expression_arg_type` the method and free-function scan
6255/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6256/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6257/// arity alone made a scan of one overload claim every same-arity sibling's call
6258/// (#2908).
6259fn constructor_overload_selection(
6260    node: Node<'_>,
6261    ctx: &ScanCtx<'_>,
6262) -> ConstructorOverloadSelection {
6263    let Some(owner) = ctx.spec.owner.as_ref() else {
6264        return ConstructorOverloadSelection::Target;
6265    };
6266    if ctx.spec.param_types.is_none() {
6267        return ConstructorOverloadSelection::Target;
6268    }
6269    // A `T var(args)` declaration keeps its arguments under the declarator or the
6270    // init declarator's value; every other shape spells an argument list that the
6271    // shared call helpers find, and its argument count needs the macro-aware
6272    // arity evidence.
6273    let (arity, arg_types) = if node.kind() == "declaration" {
6274        match declaration_constructor_initializer(node) {
6275            DeclarationConstructorInitializer::Arguments(arguments) => (
6276                argument_children(arguments).count(),
6277                argument_list_types(arguments, ctx),
6278            ),
6279            DeclarationConstructorInitializer::Expression(value) => {
6280                (1, vec![expression_arg_type(value, ctx)])
6281            }
6282            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6283        }
6284    } else {
6285        let Some(arity) = ctx
6286            .visibility
6287            .call_arity_evidence(ctx.file, node, ctx.source)
6288            .exact()
6289        else {
6290            return ConstructorOverloadSelection::Target;
6291        };
6292        (arity, call_argument_types(node, ctx))
6293    };
6294    let mut candidates = ctx
6295        .visibility
6296        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6297        .into_iter()
6298        .filter(|unit| unit.is_function())
6299        .cloned()
6300        .collect::<Vec<_>>();
6301    candidates.retain(|unit| {
6302        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6303    });
6304    if !candidates
6305        .iter()
6306        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6307    {
6308        return ConstructorOverloadSelection::Target;
6309    }
6310    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6311        candidates,
6312        &arg_types,
6313        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6314        &|candidate| {
6315            ctx.visibility
6316                .callable_is_template_declaration(&ctx.analyzer, candidate)
6317        },
6318        &|name| ctx.visibility.resolve_type(ctx.file, name),
6319        &|left, right| same_visible_symbol(left, right),
6320    );
6321    if !filtered
6322        .iter()
6323        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6324    {
6325        return ConstructorOverloadSelection::OtherOverload;
6326    }
6327    // A header declaration and its out-of-line body are one constructor even
6328    // when their persisted signature strings spell a parameter differently, so
6329    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6330    if filtered.iter().all(|candidate| {
6331        ctx.visibility
6332            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6333    }) {
6334        ConstructorOverloadSelection::Target
6335    } else {
6336        ConstructorOverloadSelection::Ambiguous
6337    }
6338}
6339
6340fn unqualified_base_initializer_constructs_target(
6341    node: Node<'_>,
6342    ctx: &ScanCtx<'_>,
6343    target_owner: &CodeUnit,
6344) -> bool {
6345    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6346        return false;
6347    }
6348    let Some(name) = node
6349        .child_by_field_name("name")
6350        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6351    else {
6352        return false;
6353    };
6354    if node_text(name, ctx.source) != ctx.spec.member_name {
6355        return false;
6356    }
6357    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6358        return false;
6359    };
6360    let inherited = ctx.visibility.inherited_injected_class_owner(
6361        &ctx.analyzer,
6362        ctx.file,
6363        &enclosing_owner,
6364        &ctx.spec.member_name,
6365    );
6366    inherited.is_some_and(|owner| {
6367        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6368    })
6369}
6370
6371fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6372    if node.kind() == "function_definition" {
6373        maybe_record_free_function_definition_hit(node, ctx);
6374        return;
6375    }
6376    if node.kind() == "function_declarator" {
6377        maybe_record_recovered_error_free_function_call(node, ctx);
6378        return;
6379    }
6380    if node.kind() == "identifier" {
6381        maybe_record_free_function_value_reference(node, ctx);
6382        return;
6383    }
6384    if node.kind() != "call_expression" {
6385        return;
6386    }
6387    let Some(function) = node
6388        .child_by_field_name("function")
6389        .or_else(|| node.named_child(0))
6390    else {
6391        return;
6392    };
6393    let text = node_text(function, ctx.source);
6394    if !name_matches_callable(text, &ctx.spec.member_name) {
6395        return;
6396    }
6397    *ctx.raw_match_count += 1;
6398    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6399        match ctx
6400            .visibility
6401            .call_arity_evidence(ctx.file, node, ctx.source)
6402            .accepts(expected)
6403        {
6404            Some(true) => {}
6405            Some(false) => return,
6406            None => {
6407                // The argument count is unknown after macro expansion. It still
6408                // cannot select a *different* target when the bare name binds
6409                // to exactly one visible callable, so let the bare-call
6410                // resolution below prove that site; every other shape stays
6411                // unproven (#1811, the scan side of the same over-conservatism
6412                // that made the forward answer discard its lone candidate).
6413                if !bare_name_binds_only_target(node, function, text, ctx) {
6414                    push_unproven_hit(function_terminal_node(function), ctx);
6415                    return;
6416                }
6417            }
6418        }
6419    }
6420    if matches!(function.kind(), "identifier" | "template_function") {
6421        let terminal = function_terminal_node(function);
6422        let name = node_text(terminal, ctx.source);
6423        if ctx.local_shadows.is_shadowed(name) {
6424            return;
6425        }
6426        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6427            && !matches!(
6428                resolve_declaring_member_owner(
6429                    &ctx.analyzer,
6430                    ctx.visibility,
6431                    ctx.file,
6432                    &enclosing_owner,
6433                    name,
6434                ),
6435                EnclosingMemberOwnerResolution::Missing
6436            )
6437        {
6438            return;
6439        }
6440        match resolve_bare_call_target(
6441            node,
6442            function,
6443            &ctx.analyzer,
6444            ctx.visibility,
6445            &ctx.ordinary_type_imports,
6446            ctx.file,
6447            ctx.source,
6448        ) {
6449            BareCallTargetResolution::FreeFunctions(units)
6450                if units
6451                    .iter()
6452                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6453            {
6454                if free_function_call_may_target(node, text, ctx) {
6455                    let recursive = enclosing_context(terminal, ctx)
6456                        .enclosing
6457                        .as_ref()
6458                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6459                    if recursive {
6460                        push_recursive_reference_hit(terminal, ctx);
6461                    } else {
6462                        push_hit(terminal, ctx);
6463                    }
6464                }
6465            }
6466            BareCallTargetResolution::UnprovenFreeFunctions(units)
6467                if units
6468                    .iter()
6469                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6470            {
6471                push_unproven_hit(terminal, ctx);
6472            }
6473            BareCallTargetResolution::FreeFunctions(_)
6474            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6475            | BareCallTargetResolution::Type(_)
6476            | BareCallTargetResolution::CallableShadow => {}
6477            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6478                push_unproven_hit(terminal, ctx);
6479            }
6480        }
6481        return;
6482    }
6483    if !free_function_call_may_target(node, text, ctx) {
6484        return;
6485    }
6486    if ctx.visibility.contains_named_symbol(
6487        ctx.file,
6488        text,
6489        TargetKind::FreeFunction,
6490        &ctx.spec.target,
6491    ) {
6492        push_hit(function_terminal_node(function), ctx);
6493    } else if ctx.visibility.resolve_known_non_target(
6494        ctx.file,
6495        text,
6496        TargetKind::FreeFunction,
6497        &ctx.spec.target,
6498    ) {
6499        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6500        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6501    } else {
6502        push_unproven_hit(function_terminal_node(function), ctx);
6503    }
6504}
6505
6506fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6507    same_visible_symbol(unit, &ctx.spec.target)
6508        || ctx
6509            .visibility
6510            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6511            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6512                &ctx.analyzer,
6513                ctx.visibility,
6514                unit,
6515                &ctx.spec.target,
6516            )
6517}
6518
6519/// Recover a bare call whose adjacent object-like macro arguments made
6520/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6521///
6522/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6523/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6524/// real block-scope function declaration remains under a `declaration`, so the
6525/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6526/// out of declaration syntax. The malformed parameter list cannot establish
6527/// arity; publish a proven hit only when ordinary visibility leaves one
6528/// callable identity for the bare name.
6529fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6530    // The macro-declaration grammar keeps the same call as a bogus
6531    // `function_definition` whose damaged declarator ends at the block
6532    // literal's `(`: its parameter list is the call's argument prefix, so
6533    // unlike the ERROR shape it carries countable arity evidence.
6534    let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6535    if !block_call_envelope
6536        && (ctx
6537            .ancestry
6538            .parent(node)
6539            .is_none_or(|parent| parent.kind() != "ERROR")
6540            || !has_ancestor_kind(node, "compound_statement"))
6541    {
6542        return;
6543    }
6544    let mut recovered_functions = Vec::new();
6545    if let Some(function) = node
6546        .child_by_field_name("declarator")
6547        .filter(|function| function.kind() == "identifier")
6548    {
6549        recovered_functions.push(function);
6550    }
6551    if let Some(parameters) = node.child_by_field_name("parameters") {
6552        let mut cursor = parameters.walk();
6553        for parameter in parameters
6554            .named_children(&mut cursor)
6555            .filter(|child| child.kind() == "parameter_declaration")
6556        {
6557            if parameter
6558                .child_by_field_name("declarator")
6559                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6560                && let Some(function) = parameter
6561                    .child_by_field_name("type")
6562                    .filter(|function| function.kind() == "type_identifier")
6563            {
6564                recovered_functions.push(function);
6565            }
6566        }
6567    }
6568
6569    for function in recovered_functions {
6570        let name = node_text(function, ctx.source);
6571        if !name_matches_callable(name, &ctx.spec.member_name)
6572            || ctx.local_shadows.is_shadowed(name)
6573        {
6574            continue;
6575        }
6576        *ctx.raw_match_count += 1;
6577        if block_call_envelope
6578            && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6579            && ctx
6580                .visibility
6581                .call_arity_evidence(ctx.file, node, ctx.source)
6582                .accepts(expected)
6583                == Some(false)
6584        {
6585            continue;
6586        }
6587        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6588            push_hit(function, ctx);
6589            continue;
6590        }
6591        let mut candidates =
6592            ctx.visibility
6593                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6594        candidates.retain(|candidate| {
6595            ctx.visibility.declaration_visible_at(
6596                &ctx.analyzer,
6597                ctx.file,
6598                candidate,
6599                function.start_byte(),
6600            )
6601        });
6602        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6603        if candidates
6604            .iter()
6605            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6606        {
6607            push_unproven_hit(function, ctx);
6608        }
6609    }
6610}
6611
6612/// Whether the bare name at `call` binds to exactly one visible callable, and
6613/// that callable is the scan target.
6614///
6615/// This is the scan-side reading of the #1811 rule: with one name binding there
6616/// is nothing an unknown argument count could select instead, so the site is a
6617/// proven reference rather than an unproven one. Only bare identifiers qualify;
6618/// a member or qualified call reaches its target through a receiver this cannot
6619/// judge.
6620fn bare_name_binds_only_target(
6621    call: Node<'_>,
6622    function: Node<'_>,
6623    text: &str,
6624    ctx: &ScanCtx<'_>,
6625) -> bool {
6626    if !matches!(function.kind(), "identifier" | "template_function") {
6627        return false;
6628    }
6629    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6630}
6631
6632fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6633    let mut candidates = ctx
6634        .visibility
6635        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6636    candidates.retain(|candidate| {
6637        ctx.visibility
6638            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6639    });
6640    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6641    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6642}
6643
6644fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6645    if ctx.spec.param_types.is_none() {
6646        return true;
6647    }
6648    let mut candidates = ctx
6649        .visibility
6650        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6651    candidates.retain(|candidate| {
6652        ctx.visibility
6653            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6654    });
6655    callable_candidates_may_match_target(call, candidates, ctx)
6656}
6657
6658fn callable_candidates_may_match_target(
6659    call: Node<'_>,
6660    mut candidates: Vec<CodeUnit>,
6661    ctx: &ScanCtx<'_>,
6662) -> bool {
6663    let Some(arity) = ctx
6664        .visibility
6665        .call_arity_evidence(ctx.file, call, ctx.source)
6666        .exact()
6667    else {
6668        return true;
6669    };
6670    candidates.retain(|unit| {
6671        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6672    });
6673    if candidates.is_empty()
6674        || !candidates
6675            .iter()
6676            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6677    {
6678        return true;
6679    }
6680    let arg_types = call_argument_types(call, ctx);
6681    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6682        candidates,
6683        &arg_types,
6684        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6685        &|candidate| {
6686            ctx.visibility
6687                .callable_is_template_declaration(&ctx.analyzer, candidate)
6688        },
6689        &|name| ctx.visibility.resolve_type(ctx.file, name),
6690        &|left, right| same_visible_symbol(left, right),
6691    );
6692    filtered
6693        .iter()
6694        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6695}
6696
6697/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6698/// in order.
6699///
6700/// A `None` entry is an argument the index cannot type; one of them makes
6701/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6702fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6703    argument_children(arguments)
6704        .map(|arg| expression_arg_type(arg, ctx))
6705        .collect()
6706}
6707
6708fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6709    call_arguments_node(call)
6710        .map(|arguments| argument_list_types(arguments, ctx))
6711        .unwrap_or_default()
6712}
6713
6714fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6715    match node.kind() {
6716        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6717        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6718            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6719            literal
6720        }),
6721        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6722        // `T v(x);` is the vexing parse: the grammar reads it as a function
6723        // declaration whose one parameter is `x` spelled as a type. A parameter
6724        // declaration that is nothing but a type identifier is that reading, and
6725        // the identifier it holds is the constructor argument.
6726        "parameter_declaration" => {
6727            let declared = node.child_by_field_name("type")?;
6728            (node.child_by_field_name("declarator").is_none()
6729                && declared.kind() == "type_identifier")
6730                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6731                .flatten()
6732        }
6733        // `this->m_params` names the same data member as the bare `m_params`.
6734        "field_expression" => {
6735            let receiver = node
6736                .child_by_field_name("argument")
6737                .or_else(|| node.named_child(0))?;
6738            let field = node.child_by_field_name("field")?;
6739            (receiver.kind() == "this")
6740                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6741                .flatten()
6742        }
6743        "parenthesized_expression" => node
6744            .child_by_field_name("argument")
6745            .or_else(|| node.named_child(0))
6746            .and_then(|inner| expression_arg_type(inner, ctx)),
6747        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
6748        // forward (#2552).
6749        "call_expression" => {
6750            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6751            expression_arg_type(forwarded, ctx)
6752        }
6753        "pointer_expression" => {
6754            let delta = match node.child_by_field_name("operator")?.kind() {
6755                "&" => 1,
6756                "*" => -1,
6757                _ => return None,
6758            };
6759            let inner = node
6760                .child_by_field_name("argument")
6761                .or_else(|| node.named_child(0))?;
6762            let mut arg_type = expression_arg_type(inner, ctx)?;
6763            arg_type.indirection += delta;
6764            Some(arg_type)
6765        }
6766        _ => None,
6767    }
6768}
6769
6770/// The type of a bare value name used as a call argument: the local or
6771/// parameter binding when one is in scope, otherwise the enclosing class's data
6772/// member of that name.
6773///
6774/// Unqualified lookup reaches a local before a data member, so a local of the
6775/// same name ends the search rather than falling through to the field. This
6776/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
6777/// the two directions agreeing about which overload a member-field argument
6778/// selects (#2894).
6779fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6780    match ctx.bindings.resolve_symbol(name) {
6781        SymbolResolution::Precise(bindings) => {
6782            bindings.iter().find_map(CppScanBinding::as_arg_type)
6783        }
6784        SymbolResolution::Ambiguous => None,
6785        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
6786            .then(|| enclosing_member_field_arg_type(node, name, ctx))
6787            .flatten(),
6788    }
6789}
6790
6791/// The declared type of `name` read as a data member of the class that lexically
6792/// owns `node`.
6793///
6794/// `None` when there is no enclosing class, when the name is not one data member
6795/// of it, or when the member's declared type does not resolve. An unknown
6796/// argument type makes the overload filter keep every candidate, which is the
6797/// honest answer for a field the index cannot type.
6798fn enclosing_member_field_arg_type(
6799    node: Node<'_>,
6800    name: &str,
6801    ctx: &ScanCtx<'_>,
6802) -> Option<CppArgType> {
6803    let owner = enclosing_context(node, ctx).owner?;
6804    let fields = ctx
6805        .visibility
6806        .visible_members_for_owner_name(ctx.file, &owner, name)
6807        .into_iter()
6808        .filter(|unit| unit.is_field())
6809        .collect::<Vec<_>>();
6810    let [field] = fields.as_slice() else {
6811        return None;
6812    };
6813    let (type_name, unit, indirection) =
6814        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
6815    Some(CppArgType {
6816        name: type_name,
6817        unit,
6818        indirection,
6819        pointee_const: false,
6820    })
6821}
6822
6823/// Record a *non-call* reference to a free function used as a value: `&foo`,
6824/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
6825/// `foo()` is recorded by the call_expression arm, and the function's own
6826/// declaration/definition name is not a reference.
6827fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6828    let text = node_text(node, ctx.source);
6829    if !name_matches_callable(text, &ctx.spec.member_name) {
6830        return;
6831    }
6832    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
6833        *ctx.raw_match_count += 1;
6834        match kind {
6835            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
6836            RecoveredCallableDeclarationKind::Definition => {
6837                push_recovered_definition_hit(node, ctx)
6838            }
6839        }
6840        return;
6841    }
6842    if is_declaration_name(node) {
6843        maybe_record_free_function_declaration_reference(node, ctx);
6844        return;
6845    }
6846    if is_call_callee_node(node) {
6847        return;
6848    }
6849    *ctx.raw_match_count += 1;
6850    if ctx.visibility.contains_named_symbol(
6851        ctx.file,
6852        text,
6853        TargetKind::FreeFunction,
6854        &ctx.spec.target,
6855    ) {
6856        push_hit(node, ctx);
6857    } else if ctx.visibility.resolve_known_non_target(
6858        ctx.file,
6859        text,
6860        TargetKind::FreeFunction,
6861        &ctx.spec.target,
6862    ) {
6863        // A qualified reference proven to a different namespace is not a match.
6864    } else {
6865        push_unproven_hit(node, ctx);
6866    }
6867}
6868
6869#[derive(Clone, Copy)]
6870enum RecoveredCallableDeclarationKind {
6871    Declaration,
6872    Definition,
6873}
6874
6875/// Recover the role of a callable name whose declaration was split by an
6876/// ERROR node. An indexed declaration range proves identity when available;
6877/// for a top-level C callable that recovery failed to range-index, same-file
6878/// name identity is sufficient because C has no function overloading. The CST
6879/// shape distinguishes a prototype from a definition. C++ parsing of
6880/// an unknown C return type can put the name under an ERROR child of the
6881/// function declarator, while call-shaped recovery puts that declarator under
6882/// an ERROR whose parent is a compound statement; only the former is admitted.
6883fn recovered_callable_declaration_kind(
6884    node: Node<'_>,
6885    ctx: &ScanCtx<'_>,
6886) -> Option<RecoveredCallableDeclarationKind> {
6887    if !matches!(node.kind(), "identifier" | "field_identifier") {
6888        return None;
6889    }
6890    let target_identity_proven =
6891        ctx.target_declaration_ranges.iter().any(|range| {
6892            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6893        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
6894            && ctx.spec.target.is_function()
6895            && ctx.spec.target.source() == ctx.file);
6896    let mut current = ctx.ancestry.parent(node);
6897    while let Some(parent) = current {
6898        if parent.is_error() {
6899            // C files with an unknown return type can retain the real
6900            // callable name as `function_declarator > ERROR`, with the ERROR
6901            // as a named child rather than the declarator field itself.
6902            let error = parent;
6903            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
6904                function.kind() == "function_declarator"
6905                    && function
6906                        .child_by_field_name("parameters")
6907                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
6908            }) else {
6909                current = ctx.ancestry.parent(parent);
6910                continue;
6911            };
6912            let container = ctx.ancestry.parent(function)?;
6913            let kind = match container.kind() {
6914                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6915                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6916                _ => return None,
6917            };
6918            return target_identity_proven.then_some(kind);
6919        }
6920        if parent.kind() == "function_declarator" {
6921            let error = ctx
6922                .ancestry
6923                .parent(parent)
6924                .filter(|parent| parent.is_error())?;
6925            let declarator = parent.child_by_field_name("declarator")?;
6926            if declarator.start_byte() > node.start_byte()
6927                || node.end_byte() > declarator.end_byte()
6928            {
6929                return None;
6930            }
6931            let container = ctx.ancestry.parent(error)?;
6932            let kind = match container.kind() {
6933                "declaration" => RecoveredCallableDeclarationKind::Declaration,
6934                "function_definition" => RecoveredCallableDeclarationKind::Definition,
6935                _ => return None,
6936            };
6937            return target_identity_proven.then_some(kind);
6938        }
6939        if parent.is_error() {
6940            current = ctx.ancestry.parent(parent);
6941            continue;
6942        }
6943        if matches!(
6944            parent.kind(),
6945            "translation_unit" | "function_definition" | "compound_statement"
6946        ) {
6947            return None;
6948        }
6949        current = ctx.ancestry.parent(parent);
6950    }
6951    None
6952}
6953
6954fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6955    if !ctx.spec.callable_has_definition_body {
6956        return;
6957    }
6958    let text = node_text(node, ctx.source);
6959    if !name_matches_callable(text, &ctx.spec.member_name) {
6960        return;
6961    }
6962    let mut declaration = ctx.ancestry.parent(node);
6963    while let Some(candidate) = declaration {
6964        if candidate.kind() == "function_definition" {
6965            return;
6966        }
6967        if candidate.kind() == "declaration" {
6968            break;
6969        }
6970        declaration = ctx.ancestry.parent(candidate);
6971    }
6972    if declaration.is_none() {
6973        return;
6974    }
6975    let linked_declaration = ctx
6976        .visibility
6977        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
6978        .into_iter()
6979        .find(|candidate| {
6980            candidate.source() == ctx.file
6981                && candidate.is_function()
6982                && (!ctx.target_group.contains(candidate)
6983                    || candidate.source() == ctx.spec.target.source())
6984                && ctx.analyzer.ranges(candidate).iter().any(|range| {
6985                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6986                })
6987                && (candidate.source() == ctx.spec.target.source()
6988                    && candidate.fq_name() == ctx.spec.target.fq_name()
6989                    && candidate.signature() == ctx.spec.target.signature()
6990                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
6991                        &ctx.analyzer,
6992                        ctx.visibility,
6993                        candidate,
6994                        &ctx.spec.target,
6995                    ))
6996                && callable_arity_matches_target(candidate, node, ctx)
6997        });
6998    if linked_declaration.is_none() {
6999        return;
7000    }
7001    *ctx.raw_match_count += 1;
7002    push_declaration_reference_hit(node, ctx);
7003}
7004
7005fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7006    let Some(function) = function_definition_name_node(node) else {
7007        return;
7008    };
7009    let text = node_text(function, ctx.source);
7010    if !name_matches_callable(text, &ctx.spec.member_name) {
7011        return;
7012    }
7013    *ctx.raw_match_count += 1;
7014    if !function_definition_signature_matches_target(node, ctx) {
7015        return;
7016    }
7017    match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
7018        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7019        DefinitionNameTargetResolution::KnownNonTarget => {
7020            // A definition in another explicit namespace is a proven non-match.
7021        }
7022        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7023    }
7024}
7025
7026fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7027    if node.kind() == "preproc_arg" {
7028        maybe_record_function_macro_replacement_method_hits(node, ctx);
7029        return;
7030    }
7031    if node.kind() == "using_declaration" {
7032        maybe_record_using_callable_hit(node, ctx);
7033        return;
7034    }
7035    if has_ancestor_kind(node, "using_declaration") {
7036        return;
7037    }
7038    if node.kind() == "function_definition" {
7039        maybe_record_method_definition_hit(node, ctx);
7040        return;
7041    }
7042    if is_declaration_name(node) {
7043        return;
7044    }
7045    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7046        maybe_record_qualified_method_value_hit(node, member, ctx);
7047        return;
7048    }
7049    if let Some(value) = qualified_callable_value(node) {
7050        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7051        return;
7052    }
7053    if let Some(call) = recovered_relational_template_member_call(node) {
7054        maybe_record_recovered_relational_template_method_hit(call, ctx);
7055        return;
7056    }
7057    if node.kind() != "call_expression" {
7058        return;
7059    }
7060    if let Some((receiver, operator)) = explicit_operator_call(node) {
7061        let text = node_text(operator, ctx.source);
7062        if !name_matches_callable(text, &ctx.spec.member_name) {
7063            return;
7064        }
7065        *ctx.raw_match_count += 1;
7066        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7067            match ctx
7068                .visibility
7069                .call_arity_evidence(ctx.file, node, ctx.source)
7070                .accepts(expected)
7071            {
7072                Some(true) => {}
7073                Some(false) => return,
7074                None => {
7075                    push_unproven_hit(operator, ctx);
7076                    return;
7077                }
7078            }
7079        }
7080        let arity = ctx
7081            .visibility
7082            .call_arity_evidence(ctx.file, node, ctx.source)
7083            .exact();
7084        record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7085        return;
7086    }
7087    let Some(function) = node
7088        .child_by_field_name("function")
7089        .or_else(|| node.named_child(0))
7090    else {
7091        return;
7092    };
7093    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7094        return;
7095    }
7096    if function.kind() == "identifier"
7097        && ctx
7098            .local_shadows
7099            .is_shadowed(node_text(function, ctx.source))
7100    {
7101        return;
7102    }
7103    *ctx.raw_match_count += 1;
7104    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7105        match ctx
7106            .visibility
7107            .call_arity_evidence(ctx.file, node, ctx.source)
7108            .accepts(expected)
7109        {
7110            Some(true) => {}
7111            Some(false) => return,
7112            None => {
7113                push_unproven_hit(function_terminal_node(function), ctx);
7114                return;
7115            }
7116        }
7117    }
7118    if !method_call_may_target(node, ctx) {
7119        return;
7120    }
7121    if is_structurally_qualified(function) {
7122        match qualified_owner_resolution(function, ctx) {
7123            QualifiedOwnerResolution::Target => {
7124                push_hit(function_terminal_node(function), ctx);
7125            }
7126            QualifiedOwnerResolution::NonTarget => {}
7127            QualifiedOwnerResolution::Unresolved => {
7128                push_unproven_hit(function_terminal_node(function), ctx);
7129            }
7130        }
7131        return;
7132    }
7133    match call_function_target_resolution(function, ctx) {
7134        MethodReceiverTargetResolution::Target
7135            if call_function_has_direct_self_receiver(
7136                function,
7137                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7138            ) =>
7139        {
7140            push_self_receiver_hit(function_terminal_node(function), ctx);
7141        }
7142        MethodReceiverTargetResolution::Target => {
7143            push_hit(function_terminal_node(function), ctx);
7144        }
7145        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7146        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7147        // the target member declared on a base is a genuine external usage of that inherited
7148        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7149        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7150        // arm because `same_owner_context` also accepts this inherited case.
7151        MethodReceiverTargetResolution::Missing
7152            if inherited_target_owner_context(function, ctx) =>
7153        {
7154            push_hit(function_terminal_node(function), ctx);
7155        }
7156        MethodReceiverTargetResolution::Missing
7157            if (matches!(function.kind(), "identifier" | "template_function")
7158                || call_function_has_direct_self_receiver(
7159                    function,
7160                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7161                ))
7162                && (same_owner_context(function, ctx)
7163                    || out_of_line_target_owner_context(function, ctx)) =>
7164        {
7165            push_self_receiver_hit(function_terminal_node(function), ctx);
7166        }
7167        MethodReceiverTargetResolution::Missing
7168            if function.kind() == "identifier"
7169                && resolves_to_lexical_free_function(function, ctx) =>
7170        {
7171            // A visible namespace/free function is a proven negative once the
7172            // enclosing structured owner and its hierarchy contain no such member.
7173        }
7174        MethodReceiverTargetResolution::Missing
7175            if !receiver_has_known_non_target(function, ctx)
7176                && !known_non_target_owner_context(function, ctx) =>
7177        {
7178            push_unproven_hit(function_terminal_node(function), ctx);
7179        }
7180        MethodReceiverTargetResolution::Missing => {}
7181    }
7182}
7183
7184/// Report member calls written inside a function-like macro's replacement.
7185///
7186/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7187/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7188/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7189/// structure, and every hit is reported at the bytes the member spells inside
7190/// that token.
7191fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7192    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7193        parent.kind() == "preproc_function_def"
7194            && parent
7195                .child_by_field_name("value")
7196                .is_some_and(|value| same_node(value, node))
7197    }) else {
7198        return;
7199    };
7200    let Some(body) = ctx
7201        .visibility
7202        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7203    else {
7204        return;
7205    };
7206    let Some(statements) = body.statements() else {
7207        return;
7208    };
7209    // Collect the candidate calls before typing anything: a replacement that
7210    // never names the queried member must not pay for its declarations.
7211    let mut candidates = Vec::new();
7212    let mut stack = vec![statements];
7213    while let Some(current) = stack.pop() {
7214        push_named_children_reversed(current, &mut stack);
7215        if current.kind() != "call_expression" {
7216            continue;
7217        }
7218        let Some(function) = current
7219            .child_by_field_name("function")
7220            .filter(|function| function.kind() == "field_expression")
7221        else {
7222            continue;
7223        };
7224        let Some(member) = function.child_by_field_name("field") else {
7225            continue;
7226        };
7227        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7228            continue;
7229        }
7230        let Some(receiver) = function
7231            .child_by_field_name("argument")
7232            .or_else(|| function.child_by_field_name("object"))
7233            .or_else(|| function.named_child(0))
7234        else {
7235            continue;
7236        };
7237        candidates.push((current, member, receiver));
7238    }
7239    if candidates.is_empty() {
7240        return;
7241    }
7242    let Some(replacement_start) =
7243        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7244    else {
7245        return;
7246    };
7247    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7248    for (current, member, receiver) in candidates {
7249        if *ctx.limit_exceeded {
7250            return;
7251        }
7252        let range = body.file_range(member, replacement_start);
7253        debug_assert_eq!(
7254            ctx.source.get(range.clone()),
7255            Some(node_text(member, &body.source)),
7256            "macro replacement member range must spell the member name"
7257        );
7258        *ctx.raw_match_count += 1;
7259        // The replacement's own macro environment is the one at the definition,
7260        // not at the sentinel offsets the recovered call carries.
7261        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7262            ctx.file,
7263            current,
7264            &body.source,
7265            definition.start_byte(),
7266        );
7267        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7268            match arity_evidence.accepts(expected) {
7269                Some(true) => {}
7270                Some(false) => continue,
7271                None => {
7272                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7273                    continue;
7274                }
7275            }
7276        }
7277        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7278        {
7279            MacroReplacementReceiver::Parameter => {
7280                if target_member_is_visible_candidate(ctx) {
7281                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7282                }
7283                continue;
7284            }
7285            MacroReplacementReceiver::Unknown => continue,
7286            MacroReplacementReceiver::Units(units) => {
7287                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7288            }
7289        };
7290        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7291            MethodReceiverTargetResolution::Target => {
7292                push_reference_hit_range(node, range.start, range.end, ctx);
7293            }
7294            MethodReceiverTargetResolution::Missing => {
7295                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7296            }
7297            MethodReceiverTargetResolution::NonTarget
7298            | MethodReceiverTargetResolution::Ambiguous => {}
7299        }
7300    }
7301}
7302
7303enum MacroReplacementReceiver {
7304    /// The receiver is one of the macro's parameters. Its type is supplied by
7305    /// each invocation, so a member call on it can never be proven here.
7306    Parameter,
7307    /// The receiver's declared type resolved to these units.
7308    Units(Vec<CodeUnit>),
7309    /// The receiver has a shape this path does not type.
7310    Unknown,
7311}
7312
7313fn macro_replacement_receiver(
7314    receiver: Node<'_>,
7315    body: &ParsedReplacementBody,
7316    locals: &HashMap<String, Option<CodeUnit>>,
7317    anchor: Node<'_>,
7318    ctx: &ScanCtx<'_>,
7319) -> MacroReplacementReceiver {
7320    let mut current = receiver;
7321    while matches!(
7322        current.kind(),
7323        "parenthesized_expression" | "pointer_expression"
7324    ) {
7325        let Some(inner) = current
7326            .child_by_field_name("argument")
7327            .or_else(|| current.named_child(0))
7328        else {
7329            return MacroReplacementReceiver::Unknown;
7330        };
7331        current = inner;
7332    }
7333    if !matches!(current.kind(), "identifier" | "field_identifier") {
7334        return MacroReplacementReceiver::Unknown;
7335    }
7336    let name = node_text(current, &body.source);
7337    if body.parameters.iter().any(|parameter| parameter == name) {
7338        return MacroReplacementReceiver::Parameter;
7339    }
7340    if let Some(local) = locals.get(name) {
7341        return match local {
7342            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7343            None => MacroReplacementReceiver::Unknown,
7344        };
7345    }
7346    // Neither a parameter nor a replacement-local: an ordinary name the macro
7347    // definition site sees, resolved exactly as the ordinary receiver path
7348    // resolves a bare identifier.
7349    let global_fields = ctx
7350        .visibility
7351        .visible_identifier_candidates(ctx.file, name)
7352        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7353        .collect::<Vec<_>>();
7354    if !global_fields.is_empty() {
7355        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7356            global_fields,
7357            anchor,
7358            ctx,
7359        ));
7360    }
7361    MacroReplacementReceiver::Units(
7362        ctx.visibility
7363            .resolve_type(ctx.file, name)
7364            .into_iter()
7365            .collect(),
7366    )
7367}
7368
7369/// Type the locals a macro replacement declares, keyed by declared name.
7370///
7371/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7372/// serves: the receiver's type is stated inside the replacement itself. A name
7373/// whose declared type does not resolve is kept with no unit, so a call on it
7374/// stays silent instead of falling through to file-scope name lookup.
7375fn macro_replacement_local_receivers(
7376    statements: Node<'_>,
7377    body: &ParsedReplacementBody,
7378    ctx: &ScanCtx<'_>,
7379) -> HashMap<String, Option<CodeUnit>> {
7380    let mut locals = HashMap::default();
7381    let mut stack = vec![statements];
7382    while let Some(current) = stack.pop() {
7383        push_named_children_reversed(current, &mut stack);
7384        if current.kind() != "declaration" {
7385            continue;
7386        }
7387        let Some(type_node) = current
7388            .child_by_field_name("type")
7389            .or_else(|| first_type_child(current))
7390        else {
7391            continue;
7392        };
7393        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7394        let mut cursor = current.walk();
7395        for child in current.named_children(&mut cursor) {
7396            let declarator = if child.kind() == "init_declarator" {
7397                child.child_by_field_name("declarator")
7398            } else {
7399                is_declarator_node(child).then_some(child)
7400            };
7401            let Some(name) =
7402                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7403            else {
7404                continue;
7405            };
7406            locals.insert(name, unit.clone());
7407        }
7408    }
7409    locals
7410}
7411
7412fn macro_replacement_declared_unit(
7413    type_node: Node<'_>,
7414    body: &ParsedReplacementBody,
7415    ctx: &ScanCtx<'_>,
7416) -> Option<CodeUnit> {
7417    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7418    let unit = match ctx
7419        .visibility
7420        .resolve_type_node_result(ctx.file, type_node, &body.source)
7421    {
7422        Ok(Some(unit)) => Some(unit),
7423        Ok(None) => ctx
7424            .visibility
7425            .canonical_type_for_reference(ctx.file, &name)
7426            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7427        Err(_) => None,
7428    }?;
7429    canonical_receiver_unit(&unit, ctx)
7430}
7431
7432/// Whether the queried member is a candidate this file can see.
7433///
7434/// A macro parameter receiver has no type at the definition, so a member call
7435/// on it is at most unproven. Requiring the target to be visible here keeps a
7436/// same-named member of an unrelated translation unit out of the result.
7437fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7438    ctx.visibility
7439        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7440        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7441}
7442
7443fn maybe_record_recovered_relational_template_method_hit(
7444    call: RecoveredRelationalTemplateMemberCall<'_>,
7445    ctx: &mut ScanCtx<'_>,
7446) {
7447    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7448        return;
7449    }
7450    *ctx.raw_match_count += 1;
7451    if !ctx
7452        .visibility
7453        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7454        || ctx
7455            .spec
7456            .callable_arity_at(call.member.start_byte())
7457            .is_some_and(|arity| !arity.accepts(call.arity))
7458    {
7459        return;
7460    }
7461    record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7462}
7463
7464fn record_explicit_receiver_method_hit(
7465    receiver: Node<'_>,
7466    member: Node<'_>,
7467    arity: Option<usize>,
7468    ctx: &mut ScanCtx<'_>,
7469) {
7470    match explicit_receiver_target_resolution(receiver, arity, ctx) {
7471        MethodReceiverTargetResolution::Target
7472            if receiver_is_self_like(
7473                receiver,
7474                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7475            ) =>
7476        {
7477            push_self_receiver_hit(member, ctx);
7478        }
7479        MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7480        MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7481        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7482    }
7483}
7484
7485fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7486    if node.kind() != "qualified_identifier" {
7487        return None;
7488    }
7489    let parameter = node
7490        .parent()
7491        .filter(|parent| parent.kind() == "parameter_declaration")?;
7492    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7493    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7494    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7495    // and `(arg)` as an abstract function declarator. Ordinary qualified
7496    // parameter types have named/pointer/reference declarators instead.
7497    if parameter.child_by_field_name("type") != Some(node)
7498        || parameter_declarator.kind() != "abstract_function_declarator"
7499    {
7500        return None;
7501    }
7502    let parameter_list = parameter
7503        .parent()
7504        .filter(|parent| parent.kind() == "parameter_list")?;
7505    if parameter_list.named_child_count() != 1 {
7506        return None;
7507    }
7508    let function_declarator = parameter_list
7509        .parent()
7510        .filter(|parent| parent.kind() == "function_declarator")?;
7511    if function_declarator
7512        .child_by_field_name("declarator")
7513        .is_none_or(|declarator| declarator.kind() != "identifier")
7514        || function_declarator
7515            .parent()
7516            .is_none_or(|parent| parent.kind() != "declaration")
7517    {
7518        return None;
7519    }
7520    node.child_by_field_name("name")
7521}
7522
7523fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7524    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7525        return;
7526    };
7527    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7528        return;
7529    }
7530    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7531        return;
7532    };
7533    *ctx.raw_match_count += 1;
7534    let owner_resolution = qualified_owner_components(imported, ctx.source)
7535        .map(|qualified| {
7536            let lexical_scope = match enclosing_lexical_scope_components(
7537                imported,
7538                &ctx.analyzer,
7539                ctx.visibility,
7540                ctx.file,
7541                ctx.source,
7542            ) {
7543                LexicalScopeResolution::Resolved(scope) => scope,
7544                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7545                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7546            };
7547            ctx.visibility.resolve_type_components_lexically(
7548                &ctx.analyzer,
7549                ctx.file,
7550                &qualified.names,
7551                qualified.global,
7552                &lexical_scope,
7553            )
7554        })
7555        .unwrap_or(LexicalTypeResolution::Missing);
7556    let matches_target_owner = matches!(
7557        owner_resolution,
7558        LexicalTypeResolution::Resolved {
7559            ref unit,
7560            ref candidates,
7561            ..
7562        } if same_visible_symbol(unit, target_owner)
7563            || candidates
7564                .iter()
7565                .any(|candidate| same_visible_symbol(candidate, target_owner))
7566    );
7567    if !matches_target_owner {
7568        match owner_resolution {
7569            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7570                push_unproven_hit(imported, ctx);
7571            }
7572            LexicalTypeResolution::Resolved { .. } => {}
7573        }
7574        return;
7575    }
7576    match visible_target_callable_member_resolution(target_owner, ctx) {
7577        VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7578        VisibleTargetCallableMemberResolution::NonCallable => {}
7579        VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7580    }
7581}
7582
7583enum VisibleTargetCallableMemberResolution {
7584    Target,
7585    NonCallable,
7586    Unresolved,
7587}
7588
7589fn visible_target_callable_member_resolution(
7590    owner: &CodeUnit,
7591    ctx: &ScanCtx<'_>,
7592) -> VisibleTargetCallableMemberResolution {
7593    match ctx
7594        .visibility
7595        .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7596    {
7597        VisibleMemberResolution::Callable(candidates)
7598            if candidates.iter().all(|candidate| {
7599                ctx.target_group.contains(candidate)
7600                    || ctx
7601                        .target_group
7602                        .iter()
7603                        .any(|target| same_visible_symbol(candidate, target))
7604            }) =>
7605        {
7606            VisibleTargetCallableMemberResolution::Target
7607        }
7608        VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7609        VisibleMemberResolution::Callable(_)
7610        | VisibleMemberResolution::AmbiguousKind
7611        | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7612    }
7613}
7614
7615fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7616    let name = node_text(node, ctx.source);
7617    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7618    let key = (namespace.clone(), name.to_string());
7619    if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7620        return resolved;
7621    }
7622    let resolved = ctx
7623        .visibility
7624        .visible_identifier_candidates(ctx.file, name)
7625        .any(|unit| {
7626            unit.is_function()
7627                && type_owner_of(&ctx.analyzer, unit).is_none()
7628                && unit.package_name() == namespace
7629        });
7630    ctx.lexical_free_function_cache
7631        .borrow_mut()
7632        .insert(key, resolved);
7633    resolved
7634}
7635
7636fn maybe_record_qualified_method_value_hit(
7637    qualified: Node<'_>,
7638    member: Node<'_>,
7639    ctx: &mut ScanCtx<'_>,
7640) {
7641    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7642        return;
7643    }
7644    *ctx.raw_match_count += 1;
7645    let resolution =
7646        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7647    match resolution {
7648        LexicalCallableValueResolution::Type(resolved_owner) => {
7649            let Some(owner) = ctx.spec.owner.as_ref() else {
7650                push_unproven_hit(member, ctx);
7651                return;
7652            };
7653            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7654                if same_visible_symbol(&resolved_owner, owner) {
7655                    push_unproven_hit(member, ctx);
7656                }
7657                return;
7658            }
7659            // An explicitly qualified method value remains an external
7660            // reference even when its owner is the enclosing class.
7661            match visible_target_callable_member_resolution(owner, ctx) {
7662                VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7663                VisibleTargetCallableMemberResolution::NonCallable => {}
7664                VisibleTargetCallableMemberResolution::Unresolved => {
7665                    push_unproven_hit(member, ctx);
7666                }
7667            }
7668        }
7669        LexicalCallableValueResolution::FreeFunction(_) => {}
7670        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7671            push_unproven_hit(member, ctx);
7672        }
7673    }
7674}
7675
7676fn qualified_callable_value_resolution(
7677    qualified: Node<'_>,
7678    member_name: &str,
7679    ctx: &ScanCtx<'_>,
7680) -> LexicalCallableValueResolution {
7681    let Some((owner_components, global)) =
7682        qualified_callable_owner_components(qualified, ctx.source)
7683    else {
7684        return LexicalCallableValueResolution::Missing;
7685    };
7686    let lexical_scope = if global {
7687        Vec::new()
7688    } else {
7689        match enclosing_lexical_scope_components(
7690            qualified,
7691            &ctx.analyzer,
7692            ctx.visibility,
7693            ctx.file,
7694            ctx.source,
7695        ) {
7696            LexicalScopeResolution::Resolved(scope) => scope,
7697            LexicalScopeResolution::Ambiguous => {
7698                return LexicalCallableValueResolution::Ambiguous;
7699            }
7700            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7701        }
7702    };
7703    if let Some(target_owner) = ctx.spec.owner.as_ref()
7704        && let LexicalTypeResolution::Resolved { unit, .. } =
7705            resolve_type_components_lexically_at_for_target_with_scope_cache(
7706                qualified,
7707                &owner_components,
7708                global,
7709                &ctx.analyzer,
7710                ctx.visibility,
7711                &ctx.ordinary_type_imports,
7712                ctx.file,
7713                ctx.source,
7714                target_owner,
7715                false,
7716                Some(&ctx.lexical_scope_cache),
7717            )
7718    {
7719        return LexicalCallableValueResolution::Type(unit);
7720    }
7721    ctx.visibility.resolve_callable_value_components_lexically(
7722        &ctx.analyzer,
7723        ctx.file,
7724        &owner_components,
7725        member_name,
7726        global,
7727        &lexical_scope,
7728    )
7729}
7730
7731fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7732    let Some(owner) = ctx.spec.owner.as_ref() else {
7733        return true;
7734    };
7735    if ctx.spec.param_types.is_none() {
7736        return true;
7737    }
7738    let candidates = ctx
7739        .visibility
7740        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7741        .into_iter()
7742        .filter(|unit| unit.is_function())
7743        .cloned()
7744        .collect::<Vec<_>>();
7745    callable_candidates_may_match_target(call, candidates, ctx)
7746}
7747
7748fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7749    let Some(function) = function_definition_name_node(node) else {
7750        return;
7751    };
7752    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7753        return;
7754    }
7755    *ctx.raw_match_count += 1;
7756    if !function_definition_signature_matches_target(node, ctx) {
7757        return;
7758    }
7759    if node_inside_target_declaration(function, ctx) {
7760        return;
7761    }
7762    if is_structurally_qualified(function) {
7763        match qualified_owner_resolution(function, ctx) {
7764            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
7765            QualifiedOwnerResolution::NonTarget => {}
7766            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7767        }
7768        return;
7769    }
7770    match definition_name_target_resolution(function, TargetKind::Method, ctx) {
7771        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7772        DefinitionNameTargetResolution::KnownNonTarget => {
7773            // A method definition for another visible owner is a proven non-match.
7774        }
7775        DefinitionNameTargetResolution::Unresolved
7776            if known_non_target_owner_context(function, ctx) => {}
7777        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7778    }
7779}
7780
7781pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7782    let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
7783    ctx.target_declaration_ranges
7784        .iter()
7785        .any(|range| range.contains(&node_range))
7786}
7787
7788fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
7789    let mut receiver = None;
7790    let mut cursor = node.walk();
7791    for child in node.named_children(&mut cursor) {
7792        if child.kind() == "argument_list" {
7793            continue;
7794        }
7795        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
7796            return receiver.map(|receiver| (receiver, operator));
7797        }
7798        if receiver.is_none() {
7799            receiver = Some(child);
7800        }
7801    }
7802    None
7803}
7804
7805/// Return the name node of a C++ function definition's declarator.
7806pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
7807    if node.kind() != "function_definition" {
7808        return None;
7809    }
7810    node.child_by_field_name("declarator")
7811        .and_then(declarator_name_node)
7812}
7813
7814fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
7815    function_definition_name_node(node)
7816}
7817
7818fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7819    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7820        return true;
7821    };
7822    let Some(candidate) = canonical_callable_at_node(
7823        node,
7824        &ctx.analyzer,
7825        ctx.visibility,
7826        ctx.file,
7827        ctx.source,
7828        ctx.spec.kind,
7829    ) else {
7830        return false;
7831    };
7832    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
7833        return false;
7834    };
7835    if !callable_arity_matches(expected, actual_arity) {
7836        return false;
7837    }
7838    ctx.spec.param_types.as_ref().is_none_or(|expected| {
7839        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
7840            .is_some_and(|actual| actual == *expected)
7841    })
7842}
7843
7844fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7845    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7846        return true;
7847    };
7848    cpp_callable_arity(&ctx.analyzer, candidate)
7849        .is_some_and(|actual| callable_arity_matches(expected, actual))
7850}
7851
7852fn callable_arity_matches(
7853    expected: brokk_bifrost_core::analyzer::model::CallableArity,
7854    actual: brokk_bifrost_core::analyzer::model::CallableArity,
7855) -> bool {
7856    expected.accepts(actual.total())
7857}
7858
7859fn canonical_callable_at_node(
7860    node: Node<'_>,
7861    analyzer: &CppGraphSource<'_>,
7862    visibility: &VisibilityIndex<'_>,
7863    file: &ProjectFile,
7864    source: &str,
7865    kind: TargetKind,
7866) -> Option<CodeUnit> {
7867    let name = callable_name_node(node)?;
7868    let name_range = (name.start_byte(), name.end_byte());
7869    let name_text = node_text(name, source);
7870    for candidate in visibility.named_candidates(file, name_text, kind) {
7871        if candidate.source() != file || !candidate.is_function() {
7872            continue;
7873        }
7874        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
7875            continue;
7876        };
7877        for occurrence in occurrences {
7878            if occurrence
7879                .name_range
7880                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
7881            {
7882                return Some(candidate);
7883            }
7884        }
7885    }
7886    None
7887}
7888
7889fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
7890    let name = if node.kind() == "function_definition" {
7891        function_definition_name_node(node)?
7892    } else {
7893        node.child_by_field_name("declarator")
7894            .and_then(declarator_name_node)?
7895    };
7896    Some(function_terminal_node(name))
7897}
7898
7899fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
7900    name_matches_callable(node_text(function_terminal_node(node), source), expected)
7901}
7902
7903enum DefinitionNameTargetResolution {
7904    Target,
7905    KnownNonTarget,
7906    Unresolved,
7907}
7908
7909fn definition_name_target_resolution(
7910    function: Node<'_>,
7911    target_kind: TargetKind,
7912    ctx: &ScanCtx<'_>,
7913) -> DefinitionNameTargetResolution {
7914    let candidates = definition_name_candidates(function, ctx);
7915    if candidates
7916        .iter()
7917        .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
7918        .any(|name| {
7919            ctx.visibility
7920                .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
7921        })
7922    {
7923        DefinitionNameTargetResolution::Target
7924    } else if candidates.iter().any(|name| {
7925        ctx.visibility
7926            .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
7927    }) {
7928        DefinitionNameTargetResolution::KnownNonTarget
7929    } else {
7930        DefinitionNameTargetResolution::Unresolved
7931    }
7932}
7933
7934fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
7935    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
7936    if raw.is_empty() {
7937        return Vec::new();
7938    }
7939    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
7940        return vec![raw];
7941    };
7942    if !raw.contains("::") {
7943        return vec![format!("{namespace}::{raw}")];
7944    }
7945    // fqname-M4: peeks at the raw first `::`-split token, including the empty
7946    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
7947    // shape as rust's `rust_reference_looks_external`); the shared structured
7948    // splitter filters empty segments, which would shift "which token is
7949    // first" for that one lead-`::` shape and is not proven equivalent here.
7950    if raw
7951        .split("::")
7952        .next()
7953        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
7954    {
7955        vec![format!("{namespace}::{raw}"), raw]
7956    } else {
7957        vec![raw]
7958    }
7959}
7960
7961fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7962    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
7963}
7964
7965/// Whether a name leaf the field scans reached sits in a value position.
7966///
7967/// Every kind those scans admit already names a value except `type_identifier`.
7968/// tree-sitter spells a non-type template argument with type syntax, so the
7969/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
7970/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
7971/// Admit that leaf only where no visible type explains the spelling, which is
7972/// the order forward navigation applies before it reads the leaf in the value
7973/// namespace (#2556). Every other `type_identifier` is a type reference and
7974/// belongs to the type scan.
7975fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7976    node.kind() != "type_identifier"
7977        || (is_type_shaped_template_argument_name(node)
7978            && ctx
7979                .visibility
7980                .resolve_type(ctx.file, node_text(node, ctx.source))
7981                .is_none())
7982}
7983
7984fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7985    if is_c_offsetof_member_node(node) {
7986        return;
7987    }
7988    if matches!(node.kind(), "identifier" | "field_identifier")
7989        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
7990            .is_some()
7991    {
7992        return;
7993    }
7994    if !matches!(
7995        node.kind(),
7996        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
7997    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7998        || !scan_leaf_is_value_position(node, ctx)
7999        || is_declaration_name(node)
8000        || is_member_field_own_declarator(node, ctx)
8001        || is_selected_field_expression_member_descendant(node)
8002        || is_nested_in_qualified_identifier(node)
8003    {
8004        return;
8005    }
8006    *ctx.raw_match_count += 1;
8007    if global_field_resolves_to_target(node, ctx) {
8008        push_hit(node, ctx);
8009    } else if global_field_is_known_non_target(node, ctx) {
8010    } else {
8011        push_unproven_hit(node, ctx);
8012    }
8013}
8014
8015/// Whether `node` belongs to the selected-member side of any enclosing field
8016/// expression. A reference may be nested arbitrarily inside the receiver side
8017/// (for example, an argument to a call-built fluent receiver), so direct child
8018/// equality is insufficient: classify each ancestor by structured subtree
8019/// containment instead.
8020fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
8021    let candidate = node;
8022    while let Some(parent) = node.parent() {
8023        if parent.kind() == "field_expression" {
8024            if let Some(field) = parent.child_by_field_name("field")
8025                && node_is_within(field, candidate)
8026            {
8027                // The `template` disambiguator in `receiver.template f<Arg>()`
8028                // wraps the selected member in a `dependent_name` whose single
8029                // named child is the `template_*` node the plain spelling puts
8030                // directly under `field`. Unwrap it so both spellings reach the
8031                // same name/arguments split (#2196).
8032                let selected = match field.kind() {
8033                    "dependent_name" => field.named_child(0).unwrap_or(field),
8034                    _ => field,
8035                };
8036                let selected_name = match selected.kind() {
8037                    "template_method" | "template_function" | "template_type" => {
8038                        selected.child_by_field_name("name").unwrap_or(selected)
8039                    }
8040                    _ => selected,
8041                };
8042                if node_is_within(selected_name, candidate) {
8043                    return true;
8044                }
8045                // A template argument is structurally inside the field subtree,
8046                // but it is an independent reference rather than the selected
8047                // member name.
8048                node = parent;
8049                continue;
8050            }
8051            let receiver = parent
8052                .child_by_field_name("argument")
8053                .or_else(|| parent.child_by_field_name("object"))
8054                .or_else(|| parent.named_child(0));
8055            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8056                // Unknown grammar shape inside a field expression: fail closed
8057                // rather than treating it as a receiver reference.
8058                return true;
8059            }
8060        }
8061        node = parent;
8062    }
8063    false
8064}
8065
8066fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8067    let text = node_text(node, ctx.source);
8068    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8069        return false;
8070    }
8071    if text.contains("::") {
8072        return ctx.visibility.contains_named_symbol(
8073            ctx.file,
8074            text,
8075            TargetKind::GlobalField,
8076            &ctx.spec.target,
8077        );
8078    }
8079    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8080        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8081    {
8082        return ctx.visibility.contains_named_symbol(
8083            ctx.file,
8084            text,
8085            TargetKind::GlobalField,
8086            &ctx.spec.target,
8087        );
8088    }
8089    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8090        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8091            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8092                brokk_bifrost_core::analyzer::Language::Cpp,
8093                &namespace,
8094            ) == indexed_scope
8095        })
8096    {
8097        return ctx.visibility.contains_named_symbol(
8098            ctx.file,
8099            text,
8100            TargetKind::GlobalField,
8101            &ctx.spec.target,
8102        );
8103    }
8104    bare_global_field_uniquely_resolves_to_target(text, ctx)
8105}
8106
8107fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8108    let mut matched_target = false;
8109    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8110        if !has_persisted_global_field_identity(unit)
8111            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8112        {
8113            continue;
8114        }
8115        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8116            continue;
8117        }
8118        if same_visible_global_field_symbol(
8119            &ctx.analyzer,
8120            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8121            unit,
8122            &ctx.spec.target,
8123        ) {
8124            matched_target = true;
8125        } else {
8126            return false;
8127        }
8128    }
8129    matched_target
8130}
8131
8132pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8133    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8134    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8135    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8136    // analyzer for every same-named candidate's parent.
8137    unit.is_field() && !unit.short_name().contains('.')
8138}
8139
8140fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8141    let text = node_text(node, ctx.source);
8142    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8143        return true;
8144    }
8145    if text.contains("::") {
8146        return ctx.visibility.resolve_known_non_target(
8147            ctx.file,
8148            text,
8149            TargetKind::GlobalField,
8150            &ctx.spec.target,
8151        );
8152    }
8153    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8154        return false;
8155    };
8156    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8157        && ctx
8158            .visibility
8159            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8160            .any(|unit| {
8161                has_persisted_global_field_identity(unit)
8162                    && unit.identifier() == ctx.spec.member_name
8163                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8164                    && !same_visible_global_field_symbol(
8165                        &ctx.analyzer,
8166                        &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8167                        unit,
8168                        &ctx.spec.target,
8169                    )
8170            })
8171}
8172
8173fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8174    if is_c_offsetof_member_node(node) {
8175        maybe_record_c_offsetof_field_hit(node, ctx);
8176        return;
8177    }
8178    if node.kind() == "field_expression" {
8179        let Some(field) = node.child_by_field_name("field") else {
8180            return;
8181        };
8182        if node_text(field, ctx.source) != ctx.spec.member_name {
8183            return;
8184        }
8185        *ctx.raw_match_count += 1;
8186        if !ctx.visibility.declaration_guards_compatible_at_reference(
8187            &ctx.analyzer,
8188            ctx.file,
8189            &ctx.spec.target,
8190            field,
8191        ) {
8192            return;
8193        }
8194        let receiver = node
8195            .child_by_field_name("argument")
8196            .or_else(|| node.child_by_field_name("object"));
8197        let receiver_resolution =
8198            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8199        match receiver_resolution {
8200            Some(MethodReceiverTargetResolution::Target)
8201                if ctx.visibility.declaration_visible_at_reference(
8202                    &ctx.analyzer,
8203                    ctx.file,
8204                    &ctx.spec.target,
8205                    field,
8206                ) || (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
8207                    && ctx.spec.target.source() == ctx.file
8208                    && ctx
8209                        .visibility
8210                        .external_type_candidate_guard_compatible_in_context(
8211                            &ctx.analyzer,
8212                            ctx.file,
8213                            &ctx.spec.target,
8214                            field,
8215                        )) =>
8216            {
8217                // The receiver proves the C++ member owner, including fields
8218                // recovered without callable activation facts. Its guard still
8219                // needs implication and stability, even in the same file.
8220                push_hit(field, ctx)
8221            }
8222            Some(
8223                MethodReceiverTargetResolution::Target | MethodReceiverTargetResolution::Missing,
8224            )
8225            | None => push_unproven_hit(field, ctx),
8226            Some(
8227                MethodReceiverTargetResolution::NonTarget
8228                | MethodReceiverTargetResolution::Ambiguous,
8229            ) => {}
8230        }
8231        return;
8232    }
8233
8234    if matches!(node.kind(), "identifier" | "field_identifier")
8235        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8236        && let Some(designator_owner) =
8237            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8238    {
8239        *ctx.raw_match_count += 1;
8240        match designator_owner {
8241            DesignatedInitializerOwner::Resolved(owner)
8242                if ctx
8243                    .spec
8244                    .owner
8245                    .as_ref()
8246                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8247            {
8248                push_hit(node, ctx);
8249            }
8250            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8251            DesignatedInitializerOwner::Resolved(_) => {}
8252        }
8253        return;
8254    }
8255
8256    let qualified_member_name_matches =
8257        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8258            && cpp_name_component_nodes(node)
8259                .and_then(|components| components.last().copied())
8260                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8261    if !matches!(
8262        node.kind(),
8263        "identifier"
8264            | "field_identifier"
8265            | "qualified_identifier"
8266            | "scoped_identifier"
8267            | "type_identifier"
8268    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8269        && !qualified_member_name_matches)
8270        || !scan_leaf_is_value_position(node, ctx)
8271        || is_declaration_name(node)
8272        || is_member_field_own_declarator(node, ctx)
8273        || is_selected_field_expression_member_descendant(node)
8274        || is_nested_in_qualified_identifier(node)
8275    {
8276        return;
8277    }
8278    *ctx.raw_match_count += 1;
8279    if is_structurally_qualified(node) {
8280        match qualified_owner_resolution(node, ctx) {
8281            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8282            QualifiedOwnerResolution::NonTarget => {}
8283            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8284        }
8285        return;
8286    }
8287    let text = node_text(node, ctx.source);
8288    if ctx.local_shadows.is_shadowed(text) {
8289        return;
8290    }
8291    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8292        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8293    let owner_context = structured_owner_context_resolution(node, ctx);
8294    if matches!(
8295        owner_context,
8296        StructuredOwnerContextResolution::SelfTarget
8297            | StructuredOwnerContextResolution::InheritedTarget
8298    ) || unscoped_enum_match
8299    {
8300        push_hit(node, ctx);
8301    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8302        .then_some(ctx.spec.owner.as_ref())
8303        .flatten()
8304    {
8305        let resolution =
8306            match resolve_active_using_enum_member(node, ctx) {
8307                ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8308                ActiveUsingEnumMemberResolution::Class(resolution) => {
8309                    if direct_class_member_shadows(node, ctx) {
8310                        return;
8311                    }
8312                    resolution
8313                }
8314                ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8315                    if let Some(owner) = structured_enclosing_owner(node, ctx) {
8316                        if direct_class_member_shadows(node, ctx) {
8317                            return;
8318                        }
8319                        let complete_same_file_leaf =
8320                            owner.source() == ctx.file
8321                                && ctx.analyzer.type_hierarchy_provider().is_some_and(
8322                                    |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
8323                                );
8324                        if !complete_same_file_leaf {
8325                            push_unproven_hit(node, ctx);
8326                            return;
8327                        }
8328                    }
8329                    match owner_context {
8330                        StructuredOwnerContextResolution::SelfTarget
8331                        | StructuredOwnerContextResolution::InheritedTarget
8332                        | StructuredOwnerContextResolution::NonTarget => return,
8333                        StructuredOwnerContextResolution::Ambiguous => {
8334                            push_unproven_hit(node, ctx);
8335                            return;
8336                        }
8337                        StructuredOwnerContextResolution::Missing => {}
8338                    }
8339                    if namespace_value_shadows(node, ctx) {
8340                        return;
8341                    }
8342                    resolution
8343                }
8344                ActiveUsingEnumMemberResolution::Missing => {
8345                    if direct_class_member_shadows(node, ctx)
8346                        || (structured_enclosing_owner(node, ctx).is_none()
8347                            && namespace_value_shadows(node, ctx))
8348                    {
8349                        return;
8350                    }
8351                    UsingEnumMemberResolution::Missing
8352                }
8353            };
8354        match resolution {
8355            UsingEnumMemberResolution::Resolved { owner, member }
8356                if same_visible_symbol(&owner, target_owner)
8357                    && same_visible_symbol(&member, &ctx.spec.target) =>
8358            {
8359                push_hit(node, ctx);
8360            }
8361            UsingEnumMemberResolution::Resolved { .. } => {}
8362            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8363                push_unproven_hit(node, ctx)
8364            }
8365        }
8366    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8367        push_unproven_hit(node, ctx);
8368    }
8369}
8370
8371fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8372    if node_text(node, ctx.source) != ctx.spec.member_name {
8373        return;
8374    }
8375    *ctx.raw_match_count += 1;
8376    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8377        push_unproven_hit(node, ctx);
8378        return;
8379    };
8380    let owner = match resolve_type_node_lexically(
8381        type_reference,
8382        &ctx.analyzer,
8383        ctx.visibility,
8384        &ctx.ordinary_type_imports,
8385        ctx.file,
8386        ctx.source,
8387    ) {
8388        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8389        LexicalTypeResolution::Resolved { .. }
8390        | LexicalTypeResolution::Ambiguous
8391        | LexicalTypeResolution::Missing => {
8392            push_unproven_hit(member, ctx);
8393            return;
8394        }
8395    };
8396    let candidates = ctx
8397        .visibility
8398        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8399        .into_iter()
8400        .filter(|candidate| candidate.is_field())
8401        .collect::<Vec<_>>();
8402    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8403        push_hit(member, ctx);
8404    } else if candidates.len() > 1 {
8405        push_unproven_hit(member, ctx);
8406    }
8407}
8408
8409enum ActiveUsingEnumMemberResolution {
8410    Block(UsingEnumMemberResolution),
8411    Class(UsingEnumMemberResolution),
8412    Namespace(UsingEnumMemberResolution),
8413    Missing,
8414}
8415
8416fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8417    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8418        ctx.visibility
8419            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8420            .into_iter()
8421            .next()
8422            .is_some()
8423    })
8424}
8425
8426fn resolve_active_using_enum_member(
8427    node: Node<'_>,
8428    ctx: &ScanCtx<'_>,
8429) -> ActiveUsingEnumMemberResolution {
8430    let block =
8431        ctx.using_enum_owners
8432            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8433    if !matches!(block, UsingEnumMemberResolution::Missing) {
8434        return ActiveUsingEnumMemberResolution::Block(block);
8435    }
8436    let class = structured_enclosing_owner(node, ctx);
8437    let namespace = enclosing_namespace_components(node, ctx.source);
8438    match ctx.semantic_using_enum_owners.resolve_member(
8439        ctx.visibility,
8440        ctx.file,
8441        class.as_ref(),
8442        &namespace,
8443        node.start_byte(),
8444        &ctx.spec.member_name,
8445    ) {
8446        SemanticUsingEnumMemberResolution::Class(resolution) => {
8447            ActiveUsingEnumMemberResolution::Class(resolution)
8448        }
8449        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8450            ActiveUsingEnumMemberResolution::Namespace(resolution)
8451        }
8452        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8453    }
8454}
8455
8456fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8457    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8458    !matches!(
8459        resolve_namespace_value(
8460            &ctx.analyzer,
8461            ctx.visibility,
8462            ctx.file,
8463            &namespace,
8464            &ctx.spec.member_name,
8465            node.start_byte(),
8466        ),
8467        NamespaceValueResolution::Missing
8468    )
8469}
8470
8471/// Whether `node` is a component of an enclosing qualified identifier's own
8472/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8473/// reference surfaced for it.
8474///
8475/// A qualified identifier owns only that path. Everything else beneath it is an
8476/// independent reference that its own target scanner must resolve:
8477///
8478/// - `Owner::Template<argument>` holds each template argument outside the path.
8479/// - Error recovery can hang a complete member initializer off an `ERROR` child
8480///   of a synthetic qualified identifier, where no structured path exists.
8481/// - The grammar accepts a `pointer_type_declarator` as a qualified
8482///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8483///   folds a whole parameter list under one `qualified_identifier`. Parameter
8484///   default values there are ordinary value expressions (#2548).
8485///
8486/// Follow the structured `scope`/`name` links rather than every ancestor, which
8487/// covers all three cases with one rule.
8488/// Whether `node` lies on the scope/name path of an enclosing
8489/// `qualified_identifier`, which owns the complete name. The grammar nests
8490/// `::A::b` and `n::A::b` as a qualified suffix `A::b`; resolving that suffix
8491/// on its own would drop the leading `::` or `n::` qualifier.
8492fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8493    let mut current = node;
8494    while let Some(parent) = current.parent() {
8495        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8496            parent
8497                .child_by_field_name(field)
8498                .is_some_and(|component| same_node(component, current))
8499        });
8500        if !on_name_path {
8501            return false;
8502        }
8503        if parent.kind() == "qualified_identifier" {
8504            return true;
8505        }
8506        current = parent;
8507    }
8508    false
8509}
8510
8511fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8512    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8513}
8514
8515/// What a receiver spelled as a qualified name denotes.
8516///
8517/// `ns::value.run()` reads the member off the declared type of the variable
8518/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8519/// Forward navigation and the inverse walk both ask this before typing such a
8520/// receiver, so the two directions accept the same receiver spellings (#2547).
8521pub enum QualifiedReceiverBase {
8522    /// Every visible declaration of the one variable the path names.
8523    Variable(Vec<CodeUnit>),
8524    /// The type the path names when it names no variable.
8525    Type(Option<CodeUnit>),
8526    /// The path names more than one variable; typing either would be a guess.
8527    Ambiguous,
8528}
8529
8530pub fn qualified_receiver_base(
8531    visibility: &VisibilityIndex<'_>,
8532    file: &ProjectFile,
8533    receiver: Node<'_>,
8534    source: &str,
8535) -> QualifiedReceiverBase {
8536    let reference = node_text(receiver, source);
8537    let fields = visibility
8538        .named_candidates(file, reference, TargetKind::GlobalField)
8539        .into_iter()
8540        .filter(has_persisted_global_field_identity)
8541        .collect::<Vec<_>>();
8542    let Some(first) = fields.first() else {
8543        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8544    };
8545    if fields
8546        .iter()
8547        .skip(1)
8548        .any(|field| !same_visible_symbol(first, field))
8549    {
8550        return QualifiedReceiverBase::Ambiguous;
8551    }
8552    QualifiedReceiverBase::Variable(fields)
8553}
8554
8555fn receiver_type_units_with_budget(
8556    node: Node<'_>,
8557    source: &str,
8558    ctx: &ScanCtx<'_>,
8559    remaining_call_depth: usize,
8560) -> Vec<CodeUnit> {
8561    let mut current = node;
8562    let mut member_chain = Vec::new();
8563    let mut base_units = loop {
8564        match current.kind() {
8565            "field_expression" => {
8566                let Some(member) = current.child_by_field_name("field") else {
8567                    return Vec::new();
8568                };
8569                let Some(receiver) = current
8570                    .child_by_field_name("argument")
8571                    .or_else(|| current.child_by_field_name("object"))
8572                    .or_else(|| current.named_child(0))
8573                else {
8574                    return Vec::new();
8575                };
8576                member_chain.push(node_text(member, source));
8577                current = receiver;
8578            }
8579            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8580                let Some(inner) = current
8581                    .child_by_field_name("argument")
8582                    .or_else(|| current.named_child(0))
8583                else {
8584                    return Vec::new();
8585                };
8586                current = inner;
8587            }
8588            // Tree-sitter uses `field_identifier` for an unqualified member
8589            // field when it appears as the base of another field expression
8590            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8591            // same structured binding and enclosing-owner paths as an
8592            // ordinary identifier; falling through to `resolve_type` would
8593            // treat the field name as a type and lose the receiver identity.
8594            "identifier" | "field_identifier" => {
8595                let name = node_text(current, source);
8596                // Function-like macro locals are visible in the replacement
8597                // range after substitution and shadow caller bindings. The
8598                // receiver scan has no root field, so recover the source tree
8599                // root before asking the visibility index for that binding.
8600                let mut root = current;
8601                while let Some(parent) = ctx.ancestry.parent(root) {
8602                    root = parent;
8603                }
8604                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8605                    ctx.file,
8606                    root,
8607                    source,
8608                    current.start_byte(),
8609                    current.end_byte(),
8610                ) && binding.name == name
8611                {
8612                    let normalized = normalize_cpp_type_name(&binding.type_name);
8613                    let unit = binding
8614                        .proven_unit
8615                        .clone()
8616                        .or_else(|| {
8617                            binding.type_node.and_then(|type_node| {
8618                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8619                            })
8620                        })
8621                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8622                    // Keep the macro declaration's shadowing boundary even
8623                    // when its type is unavailable: an unresolved macro local
8624                    // cannot be reinterpreted as a static type or outer value.
8625                    break unit.into_iter().collect();
8626                }
8627                let local = ctx.bindings.resolve_symbol(name);
8628                if let Some(bindings) = local.as_precise() {
8629                    break receiver_units_from_bindings(current, bindings, ctx);
8630                }
8631                if ctx.bindings.is_shadowed(name) {
8632                    return Vec::new();
8633                }
8634                let owner = structured_enclosing_owner(current, ctx)
8635                    .filter(CodeUnit::is_class)
8636                    .or_else(|| {
8637                        enclosing_context(current, ctx)
8638                            .owner
8639                            .filter(CodeUnit::is_class)
8640                    });
8641                if let Some(owner) = owner {
8642                    // The enclosing class is only the search root: an implicit
8643                    // member-field receiver can be declared on any base, so the
8644                    // declaring owner comes from the same hierarchy walk the
8645                    // member chain below uses, not from an exact-parent match.
8646                    let declaring_owner = match resolve_declaring_member_owner(
8647                        &ctx.analyzer,
8648                        ctx.visibility,
8649                        ctx.file,
8650                        &owner,
8651                        name,
8652                    ) {
8653                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8654                        EnclosingMemberOwnerResolution::Missing => None,
8655                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8656                    };
8657                    if let Some(declaring_owner) = declaring_owner {
8658                        let implicit_fields = ctx
8659                            .visibility
8660                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8661                            .into_iter()
8662                            .filter(|unit| unit.is_field())
8663                            .collect::<Vec<_>>();
8664                        if !implicit_fields.is_empty() {
8665                            break receiver_units_from_declared_fields(
8666                                implicit_fields,
8667                                current,
8668                                ctx,
8669                            );
8670                        }
8671                    }
8672                }
8673                let global_fields = ctx
8674                    .visibility
8675                    .visible_identifier_candidates(ctx.file, name)
8676                    .filter(|unit| {
8677                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8678                    })
8679                    .collect::<Vec<_>>();
8680                if global_fields.is_empty() {
8681                    break ctx
8682                        .visibility
8683                        .resolve_type(ctx.file, name)
8684                        .into_iter()
8685                        .collect();
8686                }
8687                if let Some(first) = global_fields.first()
8688                    && global_fields.iter().skip(1).any(|field| {
8689                        !same_visible_global_field_symbol(
8690                            &ctx.analyzer,
8691                            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8692                            first,
8693                            field,
8694                        )
8695                    })
8696                {
8697                    return Vec::new();
8698                }
8699                break receiver_units_from_declared_fields(global_fields, current, ctx);
8700            }
8701            "call_expression" | "new_expression" => {
8702                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8703                    .and_then(|binding| binding.unit)
8704                    .into_iter()
8705                    .collect();
8706            }
8707            // `((T*)p)->member` is typed by what the cast spells, not by the
8708            // operand it converts (#2981). Pointer depth does not change which
8709            // type declares the member, so only the descriptor's type matters.
8710            "cast_expression" => {
8711                let Some(descriptor) = current.child_by_field_name("type") else {
8712                    return Vec::new();
8713                };
8714                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8715                    return Vec::new();
8716                };
8717                break unit.into_iter().collect();
8718            }
8719            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8720                let name = node_text(current, source);
8721                let local = ctx.bindings.resolve_symbol(name);
8722                if let Some(bindings) = local.as_precise() {
8723                    break receiver_units_from_bindings(current, bindings, ctx);
8724                }
8725                return Vec::new();
8726            }
8727            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8728            "qualified_identifier" | "scoped_identifier" => {
8729                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8730                    QualifiedReceiverBase::Variable(fields) => {
8731                        break receiver_units_from_declared_fields(
8732                            fields.iter().collect(),
8733                            current,
8734                            ctx,
8735                        );
8736                    }
8737                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
8738                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
8739                }
8740            }
8741            _ => {
8742                break ctx
8743                    .visibility
8744                    .resolve_type(ctx.file, node_text(current, source))
8745                    .into_iter()
8746                    .collect();
8747            }
8748        }
8749    };
8750
8751    base_units = canonical_receiver_units(base_units, ctx);
8752    if base_units.is_empty() {
8753        return Vec::new();
8754    }
8755
8756    while let Some(member_name) = member_chain.pop() {
8757        let mut next_units = Vec::new();
8758        for owner in &base_units {
8759            let declaring_owner = match resolve_declaring_member_owner(
8760                &ctx.analyzer,
8761                ctx.visibility,
8762                ctx.file,
8763                owner,
8764                member_name,
8765            ) {
8766                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8767                EnclosingMemberOwnerResolution::Missing => continue,
8768                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8769            };
8770            let fields = ctx.visibility.visible_members_for_owner_name(
8771                ctx.file,
8772                &declaring_owner,
8773                member_name,
8774            );
8775            for field in fields.into_iter().filter(|unit| unit.is_field()) {
8776                let Some(unit) =
8777                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8778                        .and_then(|binding| binding.unit)
8779                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
8780                else {
8781                    continue;
8782                };
8783                if !next_units
8784                    .iter()
8785                    .any(|existing| same_visible_symbol(existing, &unit))
8786                {
8787                    next_units.push(unit);
8788                }
8789            }
8790        }
8791        if next_units.is_empty() {
8792            return Vec::new();
8793        }
8794        base_units = unanimous_receiver_units(next_units);
8795        if base_units.is_empty() {
8796            return Vec::new();
8797        }
8798    }
8799    base_units
8800}
8801
8802fn receiver_units_from_bindings(
8803    node: Node<'_>,
8804    bindings: &HashSet<CppScanBinding>,
8805    ctx: &ScanCtx<'_>,
8806) -> Vec<CodeUnit> {
8807    let mut units = Vec::new();
8808    for binding in bindings {
8809        let raw_unit = if let Some(unit) = &binding.unit {
8810            unit.clone()
8811        } else {
8812            let Some(type_name) = binding.type_name.as_deref() else {
8813                return Vec::new();
8814            };
8815            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
8816                return Vec::new();
8817            };
8818            unit
8819        };
8820        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
8821            // Type aliases are represented as class units. When an alias has
8822            // a dependent target, canonicalization deliberately preserves
8823            // that alias rather than inventing a concrete class. Give the
8824            // existing target-guided structured recovery a chance to prove
8825            // the queried owner before accepting that unresolved identity.
8826            if same_visible_symbol(&unit, &raw_unit)
8827                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
8828            {
8829                units.push(recovered);
8830                continue;
8831            }
8832            units.push(unit);
8833            continue;
8834        }
8835        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
8836            units.push(unit);
8837            continue;
8838        }
8839        return Vec::new();
8840    }
8841    unanimous_receiver_units(units)
8842}
8843
8844/// Resolve a using-alias receiver from its declaration's structured RHS when
8845/// the alias target index cannot cross a malformed namespace-sentinel node.
8846/// The inverse target owner supplies only the exact class identity to prove;
8847/// lexical AST resolution still decides whether the alias denotes that class.
8848fn recovered_receiver_alias_target(
8849    reference: Node<'_>,
8850    alias: &CodeUnit,
8851    ctx: &ScanCtx<'_>,
8852) -> Option<CodeUnit> {
8853    if !ctx
8854        .analyzer
8855        .type_alias_provider()
8856        .is_some_and(|provider| provider.is_type_alias(alias))
8857    {
8858        return None;
8859    }
8860    let target = ctx.spec.owner.as_ref()?.clone();
8861    if !target.is_class() || alias.source() != ctx.file {
8862        return None;
8863    }
8864    let range = ctx
8865        .analyzer
8866        .ranges(alias)
8867        .into_iter()
8868        .find(|range| range.start_byte < range.end_byte)?;
8869    let mut node =
8870        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8871    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
8872        node = ctx.ancestry.parent(node)?;
8873    }
8874    let type_descriptor = node.child_by_field_name("type")?;
8875    let type_node = receiver_type_node_base(type_descriptor);
8876    let resolution = resolve_type_node_lexically_for_target(
8877        type_node,
8878        &ctx.analyzer,
8879        ctx.visibility,
8880        &ctx.ordinary_type_imports,
8881        ctx.file,
8882        ctx.source,
8883        &target,
8884        Some(&ctx.lexical_scope_cache),
8885        ctx.recovered_sentinel_scope(type_node).as_deref(),
8886    );
8887    if let LexicalTypeResolution::Resolved {
8888        unit, candidates, ..
8889    } = resolution
8890        && (same_visible_symbol(&unit, &target)
8891            || candidates
8892                .iter()
8893                .any(|candidate| same_visible_symbol(candidate, &target)))
8894    {
8895        return Some(target);
8896    }
8897    let (components, global) = type_reference_components(type_node, ctx.source)?;
8898    if !global
8899        && components.len() == 2
8900        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
8901        && dependent_nested_alias_concrete_target(
8902            &ctx.analyzer,
8903            ctx.visibility,
8904            ctx.file,
8905            alias,
8906            &components[1],
8907            type_node,
8908        )
8909        .is_some_and(|unit| same_visible_symbol(&unit, &target))
8910    {
8911        return Some(target);
8912    }
8913    let scope = ctx
8914        .recovered_sentinel_scope(type_node)
8915        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8916    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
8917    let visible = ctx.visibility.external_type_candidate_visible_in_context(
8918        &ctx.analyzer,
8919        ctx.file,
8920        &target,
8921        type_node,
8922    );
8923    (path_matches && visible).then_some(target)
8924}
8925
8926/// The one concrete class that `alias`, spelled `Param::nested_name` through an
8927/// active template type parameter `Param`, can name: every other visible
8928/// class-owned alias named `nested_name` must reach the same indexed class
8929/// (#2250). `type_node` is the alias's target type node, whose guard context
8930/// each candidate must be compatible with. Competing concrete targets answer
8931/// `None`.
8932pub fn dependent_nested_alias_concrete_target(
8933    analyzer: &CppGraphSource<'_>,
8934    visibility: &VisibilityIndex<'_>,
8935    file: &ProjectFile,
8936    alias: &CodeUnit,
8937    nested_name: &str,
8938    type_node: Node<'_>,
8939) -> Option<CodeUnit> {
8940    let alias_provider = analyzer.type_alias_provider()?;
8941    let concrete = visibility
8942        .visible_identifier_candidates(file, nested_name)
8943        .filter(|candidate| {
8944            alias_provider.is_type_alias(candidate)
8945                && !same_visible_symbol(candidate, alias)
8946                && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
8947                && visibility.is_physically_visible(file, candidate)
8948                && visibility.external_type_candidate_guard_compatible_in_context(
8949                    analyzer, file, candidate, type_node,
8950                )
8951        })
8952        .filter_map(|candidate| {
8953            let canonical =
8954                visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
8955            // Another dependent alias can have the same nested name but
8956            // still canonicalize only to itself. It supplies no concrete
8957            // receiver identity and therefore cannot compete with an
8958            // alias that reaches an indexed class.
8959            (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
8960        })
8961        .collect::<Vec<_>>();
8962    match unanimous_receiver_units(concrete).as_slice() {
8963        [unit] => Some(unit.clone()),
8964        _ => None,
8965    }
8966}
8967
8968fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8969    let normalized = normalize_cpp_type_name(type_name);
8970    if normalized.is_empty() {
8971        return None;
8972    }
8973
8974    // A function-local alias is intentionally absent from the visibility
8975    // index. Recover its RHS from the structured alias declaration before
8976    // trying file-visible type lookup; this keeps the alias's lexical shadow
8977    // boundary intact.
8978    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
8979        match resolve_receiver_type_node(alias_type, ctx) {
8980            Ok(Some(unit)) => return Some(unit),
8981            Err(_) => return None,
8982            Ok(None) => {}
8983        }
8984    }
8985
8986    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
8987        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
8988        LexicalTypeResolution::Ambiguous => return None,
8989        LexicalTypeResolution::Missing => {}
8990    }
8991    let candidates = ctx
8992        .visibility
8993        .type_name_candidates(ctx.file, &normalized)
8994        .into_iter()
8995        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
8996        .collect();
8997    unanimous_receiver_units(candidates).into_iter().next()
8998}
8999
9000/// The receiver type a type node names: the visible indexed type it resolves
9001/// to, else the type its components name in the enclosing lexical scope.
9002///
9003/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
9004/// is unwrapped to its type specifier first, so every caller that has a spelled
9005/// type reads it the same way. `Err` reports a template whose arguments could
9006/// not be resolved; that is a structured failure, not an absent type.
9007fn resolve_receiver_type_node(
9008    type_node: Node<'_>,
9009    ctx: &ScanCtx<'_>,
9010) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
9011    let type_node = receiver_type_node_base(type_node);
9012    if let Some(unit) = ctx
9013        .visibility
9014        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
9015    {
9016        return Ok(Some(unit));
9017    }
9018    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
9019}
9020
9021fn resolve_receiver_type_node_lexically(
9022    type_node: Node<'_>,
9023    ctx: &ScanCtx<'_>,
9024) -> Option<CodeUnit> {
9025    let type_node = receiver_type_node_base(type_node);
9026    let components = cpp_type_name_components(type_node, ctx.source)?;
9027    let lexical_scope = match enclosing_lexical_scope_components(
9028        type_node,
9029        &ctx.analyzer,
9030        ctx.visibility,
9031        ctx.file,
9032        ctx.source,
9033    ) {
9034        LexicalScopeResolution::Resolved(scope) => scope,
9035        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
9036    };
9037    match ctx.visibility.resolve_type_components_lexically(
9038        &ctx.analyzer,
9039        ctx.file,
9040        &components,
9041        is_globally_qualified_cpp_name(type_node),
9042        &lexical_scope,
9043    ) {
9044        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
9045        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9046    }
9047}
9048
9049fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
9050    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
9051        let Some(inner) = node.child_by_field_name("type").or_else(|| {
9052            if node.kind() == "dependent_type" {
9053                node.named_child(0)
9054            } else {
9055                None
9056            }
9057        }) else {
9058            break;
9059        };
9060        node = inner;
9061    }
9062    node
9063}
9064
9065fn resolve_receiver_type_name_lexically(
9066    node: Node<'_>,
9067    normalized: &str,
9068    ctx: &ScanCtx<'_>,
9069) -> LexicalTypeResolution {
9070    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9071        brokk_bifrost_core::analyzer::Language::Cpp,
9072        normalized,
9073    );
9074    if components.is_empty() {
9075        return LexicalTypeResolution::Missing;
9076    }
9077    let lexical_scope = match enclosing_lexical_scope_components(
9078        node,
9079        &ctx.analyzer,
9080        ctx.visibility,
9081        ctx.file,
9082        ctx.source,
9083    ) {
9084        LexicalScopeResolution::Resolved(scope) => scope,
9085        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9086        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9087    };
9088    ctx.visibility.resolve_type_components_lexically(
9089        &ctx.analyzer,
9090        ctx.file,
9091        &components,
9092        normalized.starts_with("::"),
9093        &lexical_scope,
9094    )
9095}
9096
9097fn local_receiver_alias_type_node<'tree>(
9098    node: Node<'tree>,
9099    name: &str,
9100    ctx: &ScanCtx<'_>,
9101) -> Option<Node<'tree>> {
9102    let callable = nearest_callable_scope(node)?;
9103    let mut root_callable = callable;
9104    // See `target_guided_nested_type_terminal_hit`: the result carries the
9105    // caller's tree lifetime.
9106    let mut ancestor = callable.parent();
9107    while let Some(current) = ancestor {
9108        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9109            root_callable = current;
9110        }
9111        ancestor = current.parent();
9112    }
9113
9114    let mut stack = vec![root_callable];
9115    let mut best = None;
9116    while let Some(current) = stack.pop() {
9117        if current.start_byte() >= node.start_byte() {
9118            continue;
9119        }
9120        if local_type_alias_name_node(current)
9121            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9122            && current
9123                .parent()
9124                .is_none_or(|parent| parent.kind() != "template_declaration")
9125            && has_function_scope_ancestor(current)
9126            && local_alias_scope_contains_node(current, node)
9127        {
9128            let replace = best
9129                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9130            if replace {
9131                best = current.child_by_field_name("type");
9132            }
9133        }
9134        let mut cursor = current.walk();
9135        stack.extend(current.named_children(&mut cursor));
9136    }
9137    best
9138}
9139
9140fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9141    let mut canonical = Vec::with_capacity(units.len());
9142    for unit in units {
9143        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9144            return Vec::new();
9145        };
9146        canonical.push(unit);
9147    }
9148    unanimous_receiver_units(canonical)
9149}
9150
9151fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9152    if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
9153        return cached.clone();
9154    }
9155    let canonical = ctx
9156        .visibility
9157        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9158    ctx.receiver_canonical_type_cache
9159        .borrow_mut()
9160        .insert(unit.clone(), canonical.clone());
9161    canonical
9162}
9163
9164fn receiver_units_from_declared_fields(
9165    fields: Vec<&CodeUnit>,
9166    reference: Node<'_>,
9167    ctx: &ScanCtx<'_>,
9168) -> Vec<CodeUnit> {
9169    let Some(first) = fields.first() else {
9170        return Vec::new();
9171    };
9172    if fields
9173        .iter()
9174        .skip(1)
9175        .any(|field| !same_visible_symbol(first, field))
9176    {
9177        return Vec::new();
9178    }
9179    unanimous_receiver_units(
9180        fields
9181            .into_iter()
9182            .filter_map(|field| {
9183                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9184                    .and_then(|binding| binding.unit)
9185                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9186            })
9187            .collect(),
9188    )
9189}
9190
9191/// Resolve a field receiver's declared type from its structured declaration
9192/// when the persisted type fact was built under a malformed sentinel scope.
9193/// The queried member owner supplies the exact class identity to prove; the
9194/// declaration's type node and recovered lexical path provide the evidence.
9195fn recovered_receiver_field_type(
9196    reference: Node<'_>,
9197    field: &CodeUnit,
9198    ctx: &ScanCtx<'_>,
9199) -> Option<CodeUnit> {
9200    let target = ctx.spec.owner.as_ref()?.clone();
9201    if !target.is_class() || field.source() != ctx.file {
9202        return None;
9203    }
9204    let range = ctx
9205        .analyzer
9206        .ranges(field)
9207        .into_iter()
9208        .find(|range| range.start_byte < range.end_byte)?;
9209    let mut declaration =
9210        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9211    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9212        declaration = ctx.ancestry.parent(declaration)?;
9213    }
9214    let type_node = first_type_child(declaration)?;
9215    let resolution = resolve_type_node_lexically_for_target(
9216        type_node,
9217        &ctx.analyzer,
9218        ctx.visibility,
9219        &ctx.ordinary_type_imports,
9220        ctx.file,
9221        ctx.source,
9222        &target,
9223        Some(&ctx.lexical_scope_cache),
9224        ctx.recovered_sentinel_scope(type_node).as_deref(),
9225    );
9226    if let LexicalTypeResolution::Resolved {
9227        unit, candidates, ..
9228    } = resolution
9229        && (same_visible_symbol(&unit, &target)
9230            || candidates
9231                .iter()
9232                .any(|candidate| same_visible_symbol(candidate, &target)))
9233    {
9234        return Some(target);
9235    }
9236    let type_node = receiver_type_node_base(type_node);
9237    let (components, global) = type_reference_components(type_node, ctx.source)?;
9238    let scope = ctx
9239        .recovered_sentinel_scope(type_node)
9240        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9241    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9242        && ctx.visibility.external_type_candidate_visible_in_context(
9243            &ctx.analyzer,
9244            ctx.file,
9245            &target,
9246            type_node,
9247        ))
9248    .then_some(target)
9249}
9250
9251fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9252    let mut unique = Vec::new();
9253    for unit in units {
9254        if !unique
9255            .iter()
9256            .any(|existing| same_visible_symbol(existing, &unit))
9257        {
9258            unique.push(unit);
9259            if unique.len() > 1 {
9260                return Vec::new();
9261            }
9262        }
9263    }
9264    unique
9265}
9266
9267fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9268    let Some(owner) = ctx.spec.owner.as_ref() else {
9269        return false;
9270    };
9271    match node.kind() {
9272        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9273        // When that structured lookup fails, the base expression's owner is not the field's
9274        // type: treating `value_` as `Value` would misclassify
9275        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9276        // with the `this` node itself, so failing closed for an unresolved field chain does not
9277        // discard genuine self calls.
9278        "field_expression" => false,
9279        "call_expression" => node
9280            .child_by_field_name("function")
9281            .is_some_and(|function| receiver_matches_target(function, ctx)),
9282        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9283            .child_by_field_name("argument")
9284            .or_else(|| node.named_child(0))
9285            .is_some_and(|child| receiver_matches_target(child, ctx)),
9286        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9287            .bindings
9288            .resolve_symbol(node_text(node, ctx.source))
9289            .as_precise()
9290            .is_some_and(|targets| {
9291                targets
9292                    .iter()
9293                    .filter_map(|target| target.unit.as_ref())
9294                    .any(|target| {
9295                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9296                    })
9297            }),
9298        "this" => same_owner_context(node, ctx),
9299        _ => qualified_owner_matches(node, ctx),
9300    }
9301}
9302
9303fn declaring_owner_for_explicit_receiver(
9304    receiver: Node<'_>,
9305    call_arity: Option<usize>,
9306    ctx: &ScanCtx<'_>,
9307) -> EnclosingMemberOwnerResolution {
9308    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9309        return EnclosingMemberOwnerResolution::Missing;
9310    }
9311    declaring_owner_from_receiver_units(
9312        receiver_type_units(receiver, ctx.source, ctx),
9313        receiver.start_byte(),
9314        call_arity,
9315        ctx,
9316    )
9317}
9318
9319/// The owner that declares the queried member for a receiver already typed.
9320///
9321/// `reference_byte` is the reference's position in the scanned file, which
9322/// decides which declarations are visible there. A receiver recovered from a
9323/// macro replacement is typed against the sentinel parse but still reported at
9324/// its bytes in the defining file, so the two arrive separately.
9325fn declaring_owner_from_receiver_units(
9326    receiver_units: Vec<CodeUnit>,
9327    reference_byte: usize,
9328    call_arity: Option<usize>,
9329    ctx: &ScanCtx<'_>,
9330) -> EnclosingMemberOwnerResolution {
9331    let mut declaring_owner = None;
9332    for receiver_owner in receiver_units {
9333        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9334            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9335        }) {
9336            if declaring_owner
9337                .as_ref()
9338                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9339            {
9340                return EnclosingMemberOwnerResolution::Ambiguous;
9341            }
9342            declaring_owner = Some(receiver_owner);
9343            continue;
9344        }
9345        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9346        let owner_resolution = match call_arity {
9347            Some(arity) => resolve_declaring_callable_owner(
9348                &ctx.analyzer,
9349                ctx.visibility,
9350                ctx.file,
9351                ordinary,
9352                &ctx.spec.member_name,
9353                arity,
9354            ),
9355            None => ordinary,
9356        };
9357        match owner_resolution {
9358            EnclosingMemberOwnerResolution::Owner(owner) => {
9359                if declaring_owner
9360                    .as_ref()
9361                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9362                {
9363                    return EnclosingMemberOwnerResolution::Ambiguous;
9364                }
9365                declaring_owner = Some(owner);
9366            }
9367            EnclosingMemberOwnerResolution::Ambiguous => {
9368                return EnclosingMemberOwnerResolution::Ambiguous;
9369            }
9370            EnclosingMemberOwnerResolution::Missing => {}
9371        }
9372    }
9373    declaring_owner
9374        .map(EnclosingMemberOwnerResolution::Owner)
9375        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9376}
9377
9378fn declaring_owner_from_call_function(
9379    function: Node<'_>,
9380    call_arity: Option<usize>,
9381    ctx: &ScanCtx<'_>,
9382) -> Option<EnclosingMemberOwnerResolution> {
9383    match function.kind() {
9384        "field_expression" => function
9385            .child_by_field_name("argument")
9386            .or_else(|| function.child_by_field_name("object"))
9387            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9388            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9389        "call_expression" => function
9390            .child_by_field_name("function")
9391            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9392        _ => None,
9393    }
9394}
9395
9396enum MethodReceiverTargetResolution {
9397    Target,
9398    NonTarget,
9399    Ambiguous,
9400    Missing,
9401}
9402
9403fn method_receiver_target_resolution(
9404    node: Node<'_>,
9405    declaring_owner: EnclosingMemberOwnerResolution,
9406    ctx: &ScanCtx<'_>,
9407) -> MethodReceiverTargetResolution {
9408    match declaring_owner {
9409        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9410            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9411        }
9412        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9413            MethodReceiverTargetResolution::Missing
9414        }
9415        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9416            MethodReceiverTargetResolution::Target
9417        }
9418        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9419            MethodReceiverTargetResolution::NonTarget
9420        }
9421        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9422    }
9423}
9424
9425/// Decide the queried member against a receiver's declaring owner alone.
9426///
9427/// This is the part of receiver typing that needs nothing but the owner and
9428/// the reference's position, so a member call recovered from a macro
9429/// replacement reaches the same verdict as ordinary code even though its
9430/// syntax lives in a separate sentinel parse.
9431fn declaring_owner_target_resolution(
9432    declaring_owner: EnclosingMemberOwnerResolution,
9433    reference_byte: usize,
9434    ctx: &ScanCtx<'_>,
9435) -> MethodReceiverTargetResolution {
9436    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9437        return MethodReceiverTargetResolution::Missing;
9438    };
9439    match declaring_owner {
9440        EnclosingMemberOwnerResolution::Owner(owner)
9441            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9442        {
9443            MethodReceiverTargetResolution::Target
9444        }
9445        EnclosingMemberOwnerResolution::Owner(owner)
9446            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9447        {
9448            MethodReceiverTargetResolution::NonTarget
9449        }
9450        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9451            MethodReceiverTargetResolution::Missing
9452        }
9453        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9454    }
9455}
9456
9457fn explicit_receiver_target_resolution(
9458    receiver: Node<'_>,
9459    call_arity: Option<usize>,
9460    ctx: &ScanCtx<'_>,
9461) -> MethodReceiverTargetResolution {
9462    method_receiver_target_resolution(
9463        receiver,
9464        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9465        ctx,
9466    )
9467}
9468
9469fn call_function_target_resolution(
9470    function: Node<'_>,
9471    ctx: &ScanCtx<'_>,
9472) -> MethodReceiverTargetResolution {
9473    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9474        (call.kind() == "call_expression")
9475            .then(|| {
9476                ctx.visibility
9477                    .call_arity_evidence(ctx.file, call, ctx.source)
9478                    .exact()
9479            })
9480            .flatten()
9481    });
9482    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9483    else {
9484        // A bare function identifier has an implicit receiver. Do not reinterpret
9485        // that identifier as a same-named type or value before enclosing-owner
9486        // lookup gets a chance to establish the member call.
9487        return MethodReceiverTargetResolution::Missing;
9488    };
9489    method_receiver_target_resolution(function, declaring_owner, ctx)
9490}
9491
9492fn receiver_owner_matches_target(
9493    receiver_owner: &CodeUnit,
9494    target_owner: &CodeUnit,
9495    reference_byte: usize,
9496    ctx: &ScanCtx<'_>,
9497) -> bool {
9498    same_symbol(receiver_owner, target_owner)
9499        || same_logical_symbol(receiver_owner, target_owner)
9500            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9501                || (ctx.spec.owner_is_forward_declaration
9502                    && ctx
9503                        .visibility
9504                        .is_physically_visible(ctx.file, receiver_owner))
9505                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9506                || target_group_contains_owner_peer(receiver_owner, ctx)
9507                // An identical definition of the class in a header this file
9508                // includes is the target's owner too, the relation forward
9509                // definition lookup crosses from the body's side (#6902).
9510                || ctx.visibility.same_logical_class_definition(
9511                    &ctx.analyzer,
9512                    receiver_owner,
9513                    target_owner,
9514                ))
9515}
9516
9517fn receiver_owner_is_known_non_target(
9518    receiver_owner: &CodeUnit,
9519    target_owner: &CodeUnit,
9520    reference_byte: usize,
9521    ctx: &ScanCtx<'_>,
9522) -> bool {
9523    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9524        return false;
9525    }
9526    if !same_logical_symbol(receiver_owner, target_owner) {
9527        return true;
9528    }
9529    !ctx.target_group.iter().any(|target| {
9530        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9531    })
9532}
9533
9534fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9535    ctx.visibility
9536        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9537        && ctx.target_group.iter().any(|target| {
9538            type_owner_of(&ctx.analyzer, target)
9539                .as_ref()
9540                .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9541        })
9542}
9543
9544fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9545    same_symbol(candidate_owner, owner)
9546        || (same_logical_symbol(candidate_owner, owner)
9547            && candidate_owner.source() == owner.source())
9548}
9549
9550fn visible_target_peer_matches_owner(
9551    owner: &CodeUnit,
9552    reference_byte: usize,
9553    ctx: &ScanCtx<'_>,
9554) -> bool {
9555    ctx.visibility
9556        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9557        && ctx
9558            .visibility
9559            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9560            .any(|candidate| {
9561                cpp_callable_definitions_share_identity_evidence(
9562                    &ctx.analyzer,
9563                    candidate,
9564                    &ctx.spec.target,
9565                ) && ctx.visibility.declaration_visible_at(
9566                    &ctx.analyzer,
9567                    ctx.file,
9568                    candidate,
9569                    reference_byte,
9570                ) && type_owner_of(&ctx.analyzer, candidate)
9571                    .as_ref()
9572                    .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9573            })
9574}
9575
9576fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9577    match function.kind() {
9578        "field_expression" => function
9579            .child_by_field_name("argument")
9580            .or_else(|| function.child_by_field_name("object"))
9581            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9582        _ => receiver_is_self_like(function, reference_is_c),
9583    }
9584}
9585
9586fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9587    let Some(owner) = ctx.spec.owner.as_ref() else {
9588        return false;
9589    };
9590    match node.kind() {
9591        "field_expression" => node
9592            .child_by_field_name("argument")
9593            .or_else(|| node.child_by_field_name("object"))
9594            .is_some_and(|receiver| {
9595                // `this` names the enclosing class, not necessarily the class that
9596                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9597                // lookup decide whether this is a same-owner call, an inherited base
9598                // member, a nearer override, or an ambiguous base. Rejecting a derived
9599                // `this` merely because its immediate type differs from the target owner
9600                // drops genuine inherited calls (#2541).
9601                if receiver_is_self_like(
9602                    receiver,
9603                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9604                ) {
9605                    return false;
9606                }
9607                if receiver_is_known_function_declarator(receiver, ctx) {
9608                    return true;
9609                }
9610                let units = receiver_type_units(receiver, ctx.source, ctx);
9611                !units.is_empty()
9612                    && units.iter().all(|target| {
9613                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9614                    })
9615            }),
9616        "call_expression" => node
9617            .child_by_field_name("function")
9618            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9619        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9620            .child_by_field_name("argument")
9621            .or_else(|| node.named_child(0))
9622            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9623        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9624            .bindings
9625            .resolve_symbol(node_text(node, ctx.source))
9626            .as_precise()
9627            .is_some_and(|targets| {
9628                let units = targets
9629                    .iter()
9630                    .filter_map(|target| target.unit.as_ref())
9631                    .collect::<Vec<_>>();
9632                !units.is_empty()
9633                    && units.iter().all(|target| {
9634                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9635                    })
9636            }),
9637        "this" => known_non_target_owner_context(node, ctx),
9638        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9639            qualified_owner_is_known_non_target(node, ctx)
9640        }
9641        _ => false,
9642    }
9643}
9644
9645fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9646    if receiver.kind() != "identifier" {
9647        return false;
9648    }
9649    let receiver_name = node_text(receiver, ctx.source);
9650    let mut current = receiver;
9651    while let Some(parent) = ctx.ancestry.parent(current) {
9652        if parent.kind() == "compound_statement" {
9653            let mut cursor = parent.walk();
9654            for statement in parent.named_children(&mut cursor) {
9655                if statement.start_byte() >= receiver.start_byte() {
9656                    break;
9657                }
9658                if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9659                {
9660                    continue;
9661                }
9662                let type_text = statement
9663                    .child_by_field_name("type")
9664                    .or_else(|| first_type_child(statement))
9665                    .map(|type_node| node_text(type_node, ctx.source));
9666                let mut declaration_cursor = statement.walk();
9667                for child in statement.named_children(&mut declaration_cursor) {
9668                    let declarator = if child.kind() == "init_declarator" {
9669                        child.child_by_field_name("declarator")
9670                    } else if is_declarator_node(child) {
9671                        Some(child)
9672                    } else {
9673                        None
9674                    };
9675                    let Some(declarator) = declarator else {
9676                        continue;
9677                    };
9678                    if declarator.kind() == "function_declarator"
9679                        && extract_variable_name(declarator, ctx.source)
9680                            .is_some_and(|name| name == receiver_name)
9681                        && function_declarator_parameters_have_known_types(declarator, ctx)
9682                        && !constructor_style_local_declaration(
9683                            ctx.visibility,
9684                            ctx.file,
9685                            ctx.source,
9686                            declarator,
9687                            type_text,
9688                            &ctx.bindings,
9689                        )
9690                    {
9691                        return true;
9692                    }
9693                }
9694            }
9695            return false;
9696        }
9697        current = parent;
9698    }
9699    false
9700}
9701
9702fn function_declarator_parameters_have_known_types(
9703    declarator: Node<'_>,
9704    ctx: &ScanCtx<'_>,
9705) -> bool {
9706    let Some(parameters) = declarator.child_by_field_name("parameters") else {
9707        return false;
9708    };
9709    let mut cursor = parameters.walk();
9710    parameters.named_children(&mut cursor).all(|parameter| {
9711        if !matches!(
9712            parameter.kind(),
9713            "parameter_declaration" | "optional_parameter_declaration"
9714        ) {
9715            return false;
9716        }
9717        let Some(type_node) = parameter
9718            .child_by_field_name("type")
9719            .or_else(|| first_type_child(parameter))
9720        else {
9721            return false;
9722        };
9723        type_node.kind() == "primitive_type"
9724            || ctx
9725                .visibility
9726                .resolve_type_node_result(ctx.file, type_node, ctx.source)
9727                .ok()
9728                .flatten()
9729                .is_some()
9730    })
9731}
9732
9733#[derive(Clone, Copy, PartialEq, Eq)]
9734enum QualifiedOwnerResolution {
9735    Target,
9736    NonTarget,
9737    Unresolved,
9738}
9739
9740#[derive(Clone)]
9741pub enum LexicalScopeResolution {
9742    Resolved(Vec<String>),
9743    Ambiguous,
9744    Missing,
9745}
9746
9747/// One scan's memo of lexical scope reconstructions for one file, keyed by
9748/// the reconstruction's anchor range and flags, with the file's recovered
9749/// namespace regions that the key and the reconstruction both read.
9750pub struct LexicalScopeCache {
9751    resolutions: RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>,
9752    orphaned: Arc<OrphanedNamespaceScopeIndex>,
9753}
9754
9755impl LexicalScopeCache {
9756    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
9757        Self {
9758            resolutions: RefCell::new(HashMap::default()),
9759            orphaned: orphaned_namespace_scopes(visibility, file),
9760        }
9761    }
9762}
9763
9764/// The namespaces parse recovery dropped from `file`, memoized on the
9765/// analyzer (issue #1537).
9766fn orphaned_namespace_scopes(
9767    visibility: &VisibilityIndex<'_>,
9768    file: &ProjectFile,
9769) -> Arc<OrphanedNamespaceScopeIndex> {
9770    visibility
9771        .cpp()
9772        .orphaned_namespace_scopes(visibility.token(), file)
9773}
9774
9775fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9776    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
9777}
9778
9779fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9780    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
9781}
9782
9783fn is_structurally_qualified(node: Node<'_>) -> bool {
9784    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9785        && qualified_name_has_concrete_scope_separators(node)
9786}
9787
9788fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
9789    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9790        return QualifiedOwnerResolution::Unresolved;
9791    };
9792    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
9793        return QualifiedOwnerResolution::Unresolved;
9794    };
9795    // A malformed wrapper can make the parser-derived enclosing owner look
9796    // like the lexical namespace (for example tinyxml2's macro-prefixed
9797    // XMLHandle declarations). Recover the target namespace only while this
9798    // function owns scope reconstruction. An explicit recovered scope is
9799    // authoritative and enters the scoped resolver directly.
9800    if !global
9801        && !matches!(
9802            enclosing_lexical_scope_components(
9803                node,
9804                &ctx.analyzer,
9805                ctx.visibility,
9806                ctx.file,
9807                ctx.source,
9808            ),
9809            LexicalScopeResolution::Resolved(_)
9810        )
9811    {
9812        return QualifiedOwnerResolution::Unresolved;
9813    }
9814    match resolve_type_components_lexically_at_for_target_with_scope_cache(
9815        node,
9816        &components,
9817        global,
9818        &ctx.analyzer,
9819        ctx.visibility,
9820        &ctx.ordinary_type_imports,
9821        ctx.file,
9822        ctx.source,
9823        target_owner,
9824        false,
9825        Some(&ctx.lexical_scope_cache),
9826    ) {
9827        LexicalTypeResolution::Resolved { unit: owner, .. } => {
9828            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
9829                return QualifiedOwnerResolution::Target;
9830            }
9831            match cached_declaring_member_owner(&owner, ctx) {
9832                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9833                    if receiver_owner_matches_target(
9834                        &declaring_owner,
9835                        target_owner,
9836                        node.start_byte(),
9837                        ctx,
9838                    ) =>
9839                {
9840                    QualifiedOwnerResolution::Target
9841                }
9842                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9843                    if receiver_owner_is_known_non_target(
9844                        &declaring_owner,
9845                        target_owner,
9846                        node.start_byte(),
9847                        ctx,
9848                    ) =>
9849                {
9850                    QualifiedOwnerResolution::NonTarget
9851                }
9852                EnclosingMemberOwnerResolution::Owner(_)
9853                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
9854                EnclosingMemberOwnerResolution::Missing
9855                    if same_visible_symbol(&owner, target_owner) =>
9856                {
9857                    QualifiedOwnerResolution::Unresolved
9858                }
9859                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
9860            }
9861        }
9862        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
9863            QualifiedOwnerResolution::Unresolved
9864        }
9865    }
9866}
9867
9868fn qualified_callable_owner_components(
9869    node: Node<'_>,
9870    source: &str,
9871) -> Option<(Vec<String>, bool)> {
9872    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9873        || !qualified_name_has_concrete_scope_separators(node)
9874    {
9875        return None;
9876    }
9877    let owner = qualified_owner_components(node, source)?;
9878    Some((owner.names, owner.global))
9879}
9880
9881fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
9882    if !matches!(
9883        node.kind(),
9884        "identifier"
9885            | "type_identifier"
9886            | "namespace_identifier"
9887            | "qualified_identifier"
9888            | "scoped_identifier"
9889            | "scoped_type_identifier"
9890            | "template_type"
9891            | "template_function"
9892    ) {
9893        return None;
9894    }
9895    let components = cpp_type_name_components(node, source)?;
9896    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
9897}
9898
9899pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
9900    let mut namespaces = Vec::new();
9901    for parent in enclosing_cpp_namespace_nodes(node) {
9902        if let Some(name) = parent.child_by_field_name("name") {
9903            let mut components = Vec::new();
9904            if append_cpp_name_components(name, source, &mut components).is_some() {
9905                namespaces.push(components);
9906            }
9907        }
9908    }
9909    namespaces.into_iter().flatten().collect()
9910}
9911
9912pub fn enclosing_lexical_scope_components(
9913    node: Node<'_>,
9914    analyzer: &CppGraphSource<'_>,
9915    visibility: &VisibilityIndex<'_>,
9916    file: &ProjectFile,
9917    source: &str,
9918) -> LexicalScopeResolution {
9919    enclosing_lexical_scope_components_with_unresolved_owner(
9920        node,
9921        analyzer,
9922        visibility,
9923        file,
9924        source,
9925        false,
9926        false,
9927        &orphaned_namespace_scopes(visibility, file),
9928    )
9929}
9930
9931#[allow(clippy::too_many_arguments)]
9932fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
9933    node: Node<'_>,
9934    analyzer: &CppGraphSource<'_>,
9935    visibility: &VisibilityIndex<'_>,
9936    file: &ProjectFile,
9937    source: &str,
9938    allow_structured_unresolved_owner: bool,
9939    ignore_function_owner: bool,
9940    cache: Option<&LexicalScopeCache>,
9941) -> LexicalScopeResolution {
9942    let Some(cache) = cache else {
9943        return enclosing_lexical_scope_components_with_unresolved_owner(
9944            node,
9945            analyzer,
9946            visibility,
9947            file,
9948            source,
9949            allow_structured_unresolved_owner,
9950            ignore_function_owner,
9951            &orphaned_namespace_scopes(visibility, file),
9952        );
9953    };
9954    let orphaned = &cache.orphaned;
9955    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
9956    // A namespace body or translation unit anchor spans every recovered
9957    // region among its children, whose scopes differ; the region bounds keep
9958    // the key exact there and leave an anchor inside one region unchanged.
9959    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
9960        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
9961        None => (anchor_start, anchor_end),
9962    };
9963    let key = (
9964        anchor_start,
9965        anchor_end,
9966        allow_structured_unresolved_owner,
9967        ignore_function_owner,
9968    );
9969    if let Some(cached) = cache.resolutions.borrow().get(&key).cloned() {
9970        return cached;
9971    }
9972    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
9973        node,
9974        analyzer,
9975        visibility,
9976        file,
9977        source,
9978        allow_structured_unresolved_owner,
9979        ignore_function_owner,
9980        orphaned,
9981    );
9982    cache.resolutions.borrow_mut().insert(key, resolved.clone());
9983    resolved
9984}
9985
9986fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
9987    let mut current = node;
9988    loop {
9989        if matches!(
9990            current.kind(),
9991            "function_definition"
9992                | "class_specifier"
9993                | "struct_specifier"
9994                | "union_specifier"
9995                | "namespace_definition"
9996                | "translation_unit"
9997        ) {
9998            return (current.start_byte(), current.end_byte());
9999        }
10000        let Some(parent) = current.parent() else {
10001            return (current.start_byte(), current.end_byte());
10002        };
10003        current = parent;
10004    }
10005}
10006
10007#[allow(clippy::too_many_arguments)]
10008fn enclosing_lexical_scope_components_with_unresolved_owner(
10009    node: Node<'_>,
10010    analyzer: &CppGraphSource<'_>,
10011    visibility: &VisibilityIndex<'_>,
10012    file: &ProjectFile,
10013    source: &str,
10014    allow_structured_unresolved_owner: bool,
10015    ignore_function_owner: bool,
10016    orphaned: &OrphanedNamespaceScopeIndex,
10017) -> LexicalScopeResolution {
10018    #[cfg(any(test, feature = "test-support"))]
10019    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
10020    // One ancestor climb collects the namespace chain, the class chain, the
10021    // nearest function definition and both displaced-class-shape facts.
10022    // `Node::parent` re-descends from the root on every call (tree-sitter
10023    // 0.24+), so the four separate climbs this replaces each cost another
10024    // near-full-AST scan per reconstruction on a large flat file (#1927).
10025    let mut namespaces = Vec::new();
10026    let mut classes = Vec::new();
10027    let mut function_definition = None;
10028    let mut displaced_class_scope = false;
10029    let mut current = node.parent();
10030    while let Some(parent) = current {
10031        match parent.kind() {
10032            "namespace_definition" => {
10033                if let Some(name) = parent.child_by_field_name("name") {
10034                    let mut components = Vec::new();
10035                    if append_cpp_name_components(name, source, &mut components).is_some() {
10036                        namespaces.push((
10037                            parent.start_byte(),
10038                            components,
10039                            namespace_definition_has_real_braces(parent),
10040                        ));
10041                    }
10042                }
10043            }
10044            "class_specifier" | "struct_specifier" | "union_specifier" => {
10045                if let Some(name) = parent.child_by_field_name("name") {
10046                    let mut components = Vec::new();
10047                    if append_cpp_name_components(name, source, &mut components).is_some() {
10048                        classes.push(components);
10049                    }
10050                }
10051            }
10052            "function_definition" => {
10053                if function_definition.is_none() {
10054                    function_definition = Some(parent);
10055                }
10056                displaced_class_scope = displaced_class_scope
10057                    || parent.child_by_field_name("type").is_some_and(|type_node| {
10058                        matches!(
10059                            type_node.kind(),
10060                            "class_specifier" | "struct_specifier" | "union_specifier"
10061                        )
10062                    })
10063                    || is_malformed_wrapper_function_definition(parent);
10064            }
10065            _ => {}
10066        }
10067        current = parent.parent();
10068    }
10069    namespaces.reverse();
10070    // The parsed chain can stop short of the namespaces tree-sitter's error
10071    // recovery closed early; the orphaned index restores them (#1537).
10072    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
10073    let mut scope = namespace.clone();
10074    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
10075    // qualified function declarator. It is recovery scaffolding, not a real
10076    // callable owner, and must not overwrite the indexed class scope retained
10077    // by the wrapper body (#2249).
10078    let has_qualified_function_owner = function_definition
10079        .filter(|function| !is_malformed_wrapper_function_definition(*function))
10080        .and_then(function_definition_owner_lookup_node)
10081        .is_some_and(|owner| {
10082            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10083        });
10084    let indexed_scope = displaced_class_scope
10085        .then(|| {
10086            indexed_structural_class_scope(visibility, file, node, source)
10087                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10088        })
10089        .flatten()
10090        .or_else(|| {
10091            // A qualified out-of-line definition can lose its class owner from
10092            // the parser tree when a namespace sentinel or export macro wraps
10093            // the declaration.  Recover the indexed owner scope up front so
10094            // all unqualified type references in the body see the same class
10095            // boundary as C++ lookup, including aliases in parameters and
10096            // local declarations (not only template-argument leaves).
10097            (has_qualified_function_owner && function_definition.is_some())
10098                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10099                .flatten()
10100                .filter(|indexed| {
10101                    qualified_owner_scope_is_recoverable(
10102                        indexed,
10103                        &namespace,
10104                        &classes,
10105                        function_definition
10106                            .and_then(function_definition_owner_lookup_node)
10107                            .and_then(|owner| qualified_callable_owner_components(owner, source))
10108                            .map(|(components, _)| components),
10109                    )
10110                })
10111        })
10112        .or_else(|| {
10113            // A nested class declaration can likewise lose one of its outer
10114            // class ancestors from the CST.  Prefer the exact indexed
10115            // structural class scope. Its same-file declaration range and
10116            // exact template-id match already prove the owner, including a
10117            // specialization whose parser component retains only the primary
10118            // name. Keep the suffix guard for graph-only recovery, which lacks
10119            // that direct syntax-range proof.
10120            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10121                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10122                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10123                })
10124            })
10125        })
10126        .or_else(|| {
10127            // Retain the existing indexed lexical-scope recovery for
10128            // unqualified function bodies.  It is intentionally last so a
10129            // canonical class owner wins whenever one is available.
10130            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10131                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10132                .flatten()
10133                .filter(|indexed| indexed.len() > namespace.len())
10134        });
10135    if let Some(indexed_scope) = indexed_scope.as_ref() {
10136        // A macro-displaced namespace can leave the parser with the real class
10137        // body but no namespace ancestor. Prefer the structural class match;
10138        // partial specializations whose structured name cannot round-trip use
10139        // the exact indexed enclosing-owner chain instead.
10140        scope = indexed_scope.clone();
10141        classes.clear();
10142    }
10143
10144    if !ignore_function_owner
10145        && has_qualified_function_owner
10146        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10147    {
10148        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10149            return LexicalScopeResolution::Missing;
10150        };
10151        // Resolve the out-of-line owner from the parser namespace before the
10152        // provisional indexed parent can influence the answer. Per-file
10153        // extraction can assign the first same-depth using namespace to a
10154        // bare owner. The structured using resolver instead selects the
10155        // namespace whose visible class has the owner name (#1838).
10156        let imports = visibility.ordinary_type_import_cell(file);
10157        let owner_resolution = resolve_type_components_lexically_at_scoped(
10158            function,
10159            &owner,
10160            global,
10161            analyzer,
10162            visibility,
10163            &imports,
10164            file,
10165            source,
10166            None,
10167            false,
10168            false,
10169            false,
10170            namespace.clone(),
10171        );
10172        match owner_resolution {
10173            LexicalTypeResolution::Resolved {
10174                unit, components, ..
10175            } if is_indexed_class_owner(analyzer, &unit) => {
10176                scope = components;
10177                classes.clear();
10178            }
10179            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10180            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10181                match visibility
10182                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10183                {
10184                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10185                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10186                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10187                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10188                            qualified_owner_scope_is_recoverable(
10189                                indexed,
10190                                &namespace,
10191                                &classes,
10192                                Some(owner.clone()),
10193                            )
10194                        }) {
10195                            scope = indexed.clone();
10196                        } else {
10197                            scope = if global || owner.starts_with(&namespace) {
10198                                owner
10199                            } else {
10200                                let mut relative = namespace;
10201                                relative.extend(owner);
10202                                relative
10203                            };
10204                        }
10205                    }
10206                    LexicalTypeResolution::Missing => {
10207                        // Structural lexical resolution cannot see an owner class that
10208                        // is reachable only through an in-scope `using namespace`
10209                        // directive, so it would otherwise hard-fail here. The indexed
10210                        // definition already carries the true fully-qualified owner
10211                        // (its package reflects the directive), so recover the real
10212                        // enclosing scope from the analyzer graph -- exactly the scope
10213                        // chain real C++ unqualified lookup traverses. Only the strict
10214                        // callers reach this arm; the best-effort callers above keep
10215                        // their existing structural guess (and its query profile).
10216                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10217                            .or_else(|| {
10218                                indexed_namespace_qualified_scope(
10219                                    analyzer, visibility, file, node, &owner,
10220                                )
10221                            }) {
10222                            Some(indexed) => scope = indexed,
10223                            None => return LexicalScopeResolution::Missing,
10224                        }
10225                    }
10226                }
10227            }
10228        }
10229    }
10230
10231    classes.reverse();
10232    scope.extend(classes.into_iter().flatten());
10233    LexicalScopeResolution::Resolved(scope)
10234}
10235
10236fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10237    let mut current = node.parent();
10238    while let Some(parent) = current {
10239        if parent.kind() == "function_definition"
10240            && parent.child_by_field_name("type").is_some_and(|type_node| {
10241                matches!(
10242                    type_node.kind(),
10243                    "class_specifier" | "struct_specifier" | "union_specifier"
10244                )
10245            })
10246        {
10247            return true;
10248        }
10249        current = parent.parent();
10250    }
10251    false
10252}
10253
10254fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10255    let mut current = node.parent();
10256    while let Some(parent) = current {
10257        if parent.kind() == "function_definition"
10258            && is_malformed_wrapper_function_definition(parent)
10259        {
10260            return true;
10261        }
10262        current = parent.parent();
10263    }
10264    false
10265}
10266
10267/// Whether a function definition is a recovery wrapper with a non-function
10268/// declarator. The older grammar's evidence is parser damage; the
10269/// macro-declaration grammar keeps the same envelope clean, where a
10270/// `macro_modifier`/`macro_modifier_declarator`/`macro_type_specifier` head is
10271/// admitted only where the parse would otherwise contain an error.
10272pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10273    let mut cursor = node.walk();
10274    let recovery_evidence = node.has_error()
10275        || node.named_children(&mut cursor).any(|child| {
10276            matches!(
10277                child.kind(),
10278                "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10279            )
10280        });
10281    recovery_evidence
10282        && node
10283            .child_by_field_name("declarator")
10284            .is_some_and(|declarator| {
10285                declarator.kind() != "function_declarator"
10286                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10287            })
10288}
10289
10290/// Tree-sitter can make an attribute/nullability macro look like the namespace
10291/// component of a qualified function owner when it appears between the return
10292/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10293/// The recovered owner is not a C++ lexical owner, so callers resolving the
10294/// ordinary return/parameter type must retain the surrounding namespace scope.
10295fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10296    node.child_by_field_name("scope")
10297        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10298        .is_some()
10299}
10300
10301fn indexed_structural_class_scope(
10302    visibility: &VisibilityIndex<'_>,
10303    file: &ProjectFile,
10304    node: Node<'_>,
10305    source: &str,
10306) -> Option<Vec<String>> {
10307    let mut current = node.parent();
10308    while let Some(parent) = current {
10309        if matches!(
10310            parent.kind(),
10311            "class_specifier" | "struct_specifier" | "union_specifier"
10312        ) {
10313            return visibility.indexed_structural_class_scope(file, parent, source);
10314        }
10315        current = parent.parent();
10316    }
10317    None
10318}
10319
10320/// Check that an indexed owner scope is a structured completion of the parser
10321/// scope rather than an unrelated same-spelled declaration.
10322///
10323/// Error recovery around C++ namespace sentinels can preserve only a subset of
10324/// the namespace/class chain.  The indexed definition still carries the full
10325/// owner path, so require every surviving parser component to occur in order
10326/// and require any explicit qualified function owner to be the terminal
10327/// suffix.  An empty parser scope is accepted only with that qualified-owner
10328/// suffix evidence; a lone top-level short name is not evidence that a
10329/// namespace was lost.
10330fn qualified_owner_scope_is_recoverable(
10331    indexed: &[String],
10332    namespace: &[String],
10333    classes: &[Vec<String>],
10334    qualified_owner: Option<Vec<String>>,
10335) -> bool {
10336    if let Some(owner) = qualified_owner {
10337        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10338            return false;
10339        }
10340        // A malformed namespace sentinel can erase every parser namespace
10341        // ancestor.  The indexed enclosing callable still provides an
10342        // authoritative class owner, so the qualified owner suffix itself is
10343        // enough evidence in that case.  When namespace components survived,
10344        // retain the stricter subsequence check below.
10345        if namespace.is_empty() {
10346            return true;
10347        }
10348        if indexed.len() <= namespace.len() {
10349            return false;
10350        }
10351        return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10352    }
10353    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10354    if !class_components.is_empty() {
10355        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10356    }
10357    if namespace.is_empty() || indexed.len() <= namespace.len() {
10358        return false;
10359    }
10360    super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10361}
10362
10363/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10364/// counts as the true lexical owner recovered from the indexed graph.
10365fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10366    unit.is_class()
10367        && !analyzer
10368            .type_alias_provider()
10369            .is_some_and(|provider| provider.is_type_alias(unit))
10370}
10371
10372/// Recover the enclosing member's true lexical scope from the *indexed*
10373/// definition when structural resolution cannot see the owner class.
10374///
10375/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10376/// {...}`) whose owner class is reachable only through an in-scope `using
10377/// namespace X;` directive cannot be resolved by `resolve_type_components_
10378/// lexically`, which walks structural lexical tiers and never consults
10379/// using-directives. The definition itself, however, is indexed with its true
10380/// fully-qualified identity (its package already reflects the directive), so
10381/// the analyzer graph knows the real owner. Walk from the reference's indexed
10382/// enclosing code unit up to the innermost enclosing class and return that
10383/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10384/// -- exactly the scope chain C++ unqualified lookup traverses.
10385fn indexed_enclosing_owner_scope(
10386    analyzer: &CppGraphSource<'_>,
10387    visibility: &VisibilityIndex<'_>,
10388    file: &ProjectFile,
10389    node: Node<'_>,
10390) -> Option<Vec<String>> {
10391    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10392}
10393
10394/// Recover the lexical scope of an out-of-line definition whose syntactic
10395/// qualifier names a *namespace* rather than a class (`void out::target(int)
10396/// {...}` written at file scope, issue #3096).
10397///
10398/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10399/// nothing here and the strict caller would fail closed for every unqualified
10400/// reference in the body. C++ unqualified lookup inside such a definition
10401/// proceeds from the named namespace outward, exactly as inside a `namespace
10402/// out { ... }` block, and the indexed definition already carries that scope
10403/// (`["out"]` for `out::target`).
10404///
10405/// Take it only when a namespace of that name is visible from the file and the
10406/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10407/// the graph does not hold (`void MissingContainer::call()`) has no such
10408/// namespace, so it still fails closed -- and it is rejected by the in-memory
10409/// visibility index, before any indexed enclosing-unit query.
10410fn indexed_namespace_qualified_scope(
10411    analyzer: &CppGraphSource<'_>,
10412    visibility: &VisibilityIndex<'_>,
10413    file: &ProjectFile,
10414    node: Node<'_>,
10415    owner: &[String],
10416) -> Option<Vec<String>> {
10417    let name = owner.last().expect("a qualified owner has one component");
10418    if !visibility
10419        .visible_identifier_candidates(file, name)
10420        .any(|unit| unit.is_module())
10421    {
10422        return None;
10423    }
10424    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10425    indexed.ends_with(owner).then_some(indexed)
10426}
10427
10428fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10429    let start = enclosing_context(node, ctx).enclosing?;
10430    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10431        ctx.analyzer.parent_of(unit)
10432    })
10433    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10434}
10435
10436pub fn resolve_type_node_lexically(
10437    node: Node<'_>,
10438    analyzer: &CppGraphSource<'_>,
10439    visibility: &VisibilityIndex<'_>,
10440    ordinary_type_imports: &OrdinaryTypeImportCell,
10441    file: &ProjectFile,
10442    source: &str,
10443) -> LexicalTypeResolution {
10444    let Some((components, global)) = type_reference_components(node, source) else {
10445        return LexicalTypeResolution::Missing;
10446    };
10447    let resolution = resolve_type_components_lexically_at(
10448        node,
10449        &components,
10450        global,
10451        analyzer,
10452        visibility,
10453        ordinary_type_imports,
10454        file,
10455        source,
10456    );
10457    if !is_cpp_template_argument_type_leaf(node) {
10458        return resolution;
10459    }
10460
10461    // Error recovery can detach a member function from its class while
10462    // leaving an unqualified type argument (for example `error_type` in
10463    // `expected<..., error_type>`). The normal structural scope then lacks
10464    // the class owner and resolves the wrong same-spelled alias, or fails
10465    // closed. The indexed enclosing unit still carries the authoritative
10466    // class scope; retry only this narrowly-shaped leaf with that scope.
10467    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10468        return resolution;
10469    };
10470    let namespace_scope = enclosing_namespace_components(node, source);
10471    if indexed_scope.len() <= namespace_scope.len() {
10472        return resolution;
10473    }
10474    let indexed = resolve_type_components_in_authoritative_scope(
10475        node,
10476        &components,
10477        global,
10478        analyzer,
10479        visibility,
10480        ordinary_type_imports,
10481        file,
10482        source,
10483        None,
10484        false,
10485        false,
10486        false,
10487        indexed_scope,
10488    );
10489    match indexed {
10490        LexicalTypeResolution::Resolved { ref unit, .. }
10491            if !visibility
10492                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10493        {
10494            resolution
10495        }
10496        LexicalTypeResolution::Resolved { .. } => indexed,
10497        _ => resolution,
10498    }
10499}
10500
10501/// Resolve a type lexically, using the target to guide incomplete evidence.
10502/// A lexical resolution visible in context wins over target guidance even when
10503/// its unit and candidates do not include the target.
10504#[allow(clippy::too_many_arguments)]
10505pub fn resolve_type_node_lexically_for_target(
10506    node: Node<'_>,
10507    analyzer: &CppGraphSource<'_>,
10508    visibility: &VisibilityIndex<'_>,
10509    ordinary_type_imports: &OrdinaryTypeImportCell,
10510    file: &ProjectFile,
10511    source: &str,
10512    target: &CodeUnit,
10513    scope_cache: Option<&LexicalScopeCache>,
10514    recovered_scope: Option<&[String]>,
10515) -> LexicalTypeResolution {
10516    let Some((reference_components, global)) = type_reference_components(node, source) else {
10517        return LexicalTypeResolution::Missing;
10518    };
10519    let terminal = reference_components
10520        .last()
10521        .expect("type reference components are non-empty");
10522    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
10523        return LexicalTypeResolution::Missing;
10524    }
10525    let template_arguments = cpp_template_reference_arguments(node, source);
10526    let selects_concrete_specialization =
10527        template_arguments.is_some() && visibility.is_template_specialization(target);
10528    if !selects_concrete_specialization
10529        && !visibility.structured_type_reference_may_resolve_to_target(
10530            analyzer,
10531            file,
10532            std::slice::from_ref(terminal),
10533            false,
10534            &[],
10535            target,
10536        )
10537    {
10538        return LexicalTypeResolution::Missing;
10539    }
10540    if let Some(arguments) = template_arguments.as_ref() {
10541        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
10542            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10543                node,
10544                &reference_components,
10545                global,
10546                analyzer,
10547                visibility,
10548                ordinary_type_imports,
10549                file,
10550                source,
10551                recovered_scope,
10552            )
10553        } else {
10554            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10555                node,
10556                &reference_components,
10557                global,
10558                analyzer,
10559                visibility,
10560                ordinary_type_imports,
10561                file,
10562                source,
10563                scope_cache,
10564            )
10565        };
10566        return match alias_resolution {
10567            LexicalTypeResolution::Resolved {
10568                unit,
10569                components,
10570                candidates,
10571            } if visibility.template_alias_arguments_preserve_target(
10572                analyzer, file, &unit, arguments, target,
10573            ) =>
10574            {
10575                LexicalTypeResolution::Resolved {
10576                    unit: target.clone(),
10577                    components,
10578                    candidates,
10579                }
10580            }
10581            LexicalTypeResolution::Resolved {
10582                unit,
10583                components,
10584                candidates,
10585            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
10586                Ok(resolved_unit) => {
10587                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
10588                        .then(|| {
10589                            target_guided_malformed_template_alias_resolution(
10590                                node,
10591                                analyzer,
10592                                visibility,
10593                                file,
10594                                arguments,
10595                                &reference_components,
10596                                target,
10597                            )
10598                        })
10599                        .flatten();
10600                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
10601                        unit: resolved_unit,
10602                        components,
10603                        candidates,
10604                    })
10605                }
10606                Err(_) => LexicalTypeResolution::Ambiguous,
10607            },
10608            LexicalTypeResolution::Missing => {
10609                let target_preserving = if let Some(recovered_scope) = recovered_scope {
10610                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
10611                        node,
10612                        &reference_components,
10613                        global,
10614                        analyzer,
10615                        visibility,
10616                        ordinary_type_imports,
10617                        file,
10618                        source,
10619                        target,
10620                        true,
10621                        recovered_scope,
10622                    )
10623                } else {
10624                    resolve_type_components_lexically_at_for_target_with_scope_cache(
10625                        node,
10626                        &reference_components,
10627                        global,
10628                        analyzer,
10629                        visibility,
10630                        ordinary_type_imports,
10631                        file,
10632                        source,
10633                        target,
10634                        true,
10635                        scope_cache,
10636                    )
10637                };
10638                match target_preserving {
10639                    LexicalTypeResolution::Resolved {
10640                        unit: _,
10641                        components,
10642                        candidates,
10643                    } if template_reference_candidates_select_target(
10644                        node,
10645                        &candidates,
10646                        analyzer,
10647                        visibility,
10648                        file,
10649                        source,
10650                        target,
10651                    ) =>
10652                    {
10653                        LexicalTypeResolution::Resolved {
10654                            unit: target.clone(),
10655                            components,
10656                            candidates,
10657                        }
10658                    }
10659                    _ => target_guided_malformed_template_alias_resolution(
10660                        node,
10661                        analyzer,
10662                        visibility,
10663                        file,
10664                        arguments,
10665                        &reference_components,
10666                        target,
10667                    )
10668                    .unwrap_or(LexicalTypeResolution::Missing),
10669                }
10670            }
10671            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
10672        };
10673    }
10674    let resolution = if let Some(recovered_scope) = recovered_scope {
10675        resolve_type_components_lexically_at_for_target_with_recovered_scope(
10676            node,
10677            &reference_components,
10678            global,
10679            analyzer,
10680            visibility,
10681            ordinary_type_imports,
10682            file,
10683            source,
10684            target,
10685            true,
10686            recovered_scope,
10687        )
10688    } else {
10689        resolve_type_components_lexically_at_for_target_with_scope_cache(
10690            node,
10691            &reference_components,
10692            global,
10693            analyzer,
10694            visibility,
10695            ordinary_type_imports,
10696            file,
10697            source,
10698            target,
10699            true,
10700            scope_cache,
10701        )
10702    };
10703    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
10704        target_guided_qualified_namespace_function_type_resolution(
10705            node,
10706            &reference_components,
10707            global,
10708            analyzer,
10709            visibility,
10710            ordinary_type_imports,
10711            file,
10712            source,
10713            target,
10714        )
10715        .unwrap_or(resolution)
10716    } else {
10717        resolution
10718    };
10719    if !is_cpp_template_argument_type_leaf(node) {
10720        return resolution;
10721    }
10722
10723    // Preprocessor recovery can lift a member declaration out of its class
10724    // field list.  The unqualified template argument is then resolved from
10725    // the namespace only, even though the indexed enclosing callable still
10726    // identifies the class owner.  Retry this exact leaf against that
10727    // structured owner scope; ordinary type nodes must continue to use the
10728    // parser-derived lexical scope so unrelated same-spelled aliases remain
10729    // excluded.
10730    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10731        return resolution;
10732    };
10733    let namespace_scope = enclosing_namespace_components(node, source);
10734    if indexed_scope.len() <= namespace_scope.len() {
10735        return resolution;
10736    }
10737    let indexed = visibility.resolve_type_components_lexically_for_target(
10738        analyzer,
10739        file,
10740        &reference_components,
10741        global,
10742        &indexed_scope,
10743        target,
10744    );
10745    match indexed {
10746        LexicalTypeResolution::Resolved { ref unit, .. }
10747            if visibility
10748                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10749        {
10750            indexed
10751        }
10752        _ => resolution,
10753    }
10754}
10755
10756/// Recover the lexical namespace of an out-of-line namespace function whose
10757/// relative qualifier is reachable through a `using namespace` directive.
10758///
10759/// Per-file declaration extraction can attach `void schema::consume(...)` to
10760/// the first visible namespace prefix when more than one using-directive is
10761/// active. A matching visible free-function declaration still proves the
10762/// complete namespace. Target-guided inverse lookup may use that namespace to
10763/// retry a parameter or body type, but only when the target lives in the same
10764/// namespace and the declaration's callable arity matches the definition.
10765#[allow(clippy::too_many_arguments)]
10766fn target_guided_qualified_namespace_function_type_resolution(
10767    node: Node<'_>,
10768    components: &[String],
10769    global: bool,
10770    analyzer: &CppGraphSource<'_>,
10771    visibility: &VisibilityIndex<'_>,
10772    ordinary_type_imports: &OrdinaryTypeImportCell,
10773    file: &ProjectFile,
10774    source: &str,
10775    target: &CodeUnit,
10776) -> Option<LexicalTypeResolution> {
10777    let function_definition = std::iter::successors(Some(node), |current| current.parent())
10778        .find(|current| current.kind() == "function_definition")?;
10779    let function = function_definition_name_node(function_definition)?;
10780    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
10781    let target_namespace = target.package_name();
10782    if target_namespace.is_empty() {
10783        return None;
10784    }
10785    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10786        brokk_bifrost_core::analyzer::Language::Cpp,
10787        target_namespace,
10788    );
10789    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
10790    {
10791        return None;
10792    }
10793
10794    let function_name = node_text(function_terminal_node(function), source);
10795    let definition_unit = canonical_callable_at_node(
10796        function_definition,
10797        analyzer,
10798        visibility,
10799        file,
10800        source,
10801        TargetKind::FreeFunction,
10802    )?;
10803    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
10804    let declaration_proves_namespace = visibility
10805        .visible_identifier_candidates(file, function_name)
10806        .filter(|candidate| {
10807            candidate.is_function()
10808                && type_owner_of(analyzer, candidate).is_none()
10809                && candidate.package_name() == target_namespace
10810        })
10811        .any(|candidate| {
10812            cpp_callable_arity(analyzer, candidate)
10813                .is_none_or(|known| known.accepts(definition_arity))
10814        });
10815    if !declaration_proves_namespace {
10816        return None;
10817    }
10818
10819    let resolution = resolve_type_components_lexically_at_scoped(
10820        node,
10821        components,
10822        global,
10823        analyzer,
10824        visibility,
10825        ordinary_type_imports,
10826        file,
10827        source,
10828        Some(target),
10829        true,
10830        false,
10831        false,
10832        target_scope,
10833    );
10834    match &resolution {
10835        LexicalTypeResolution::Resolved {
10836            unit, candidates, ..
10837        } if (same_visible_symbol(unit, target)
10838            || candidates
10839                .iter()
10840                .any(|candidate| same_visible_symbol(candidate, target)))
10841            && visibility
10842                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10843        {
10844            Some(resolution)
10845        }
10846        LexicalTypeResolution::Resolved { .. }
10847        | LexicalTypeResolution::Ambiguous
10848        | LexicalTypeResolution::Missing => None,
10849    }
10850}
10851
10852#[allow(clippy::too_many_arguments)]
10853fn target_guided_malformed_template_alias_resolution(
10854    node: Node<'_>,
10855    analyzer: &CppGraphSource<'_>,
10856    visibility: &VisibilityIndex<'_>,
10857    file: &ProjectFile,
10858    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
10859    components: &[String],
10860    target: &CodeUnit,
10861) -> Option<LexicalTypeResolution> {
10862    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
10863        return None;
10864    }
10865
10866    let identifier = &components[0];
10867    let namespace =
10868        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
10869    let namespace_name = namespace.join("::");
10870    let candidates = visibility
10871        .visible_identifier_candidates(file, identifier)
10872        .filter(|candidate| {
10873            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
10874                && visibility.type_candidate_may_be_visible_before_reference(
10875                    analyzer,
10876                    file,
10877                    candidate,
10878                    node.start_byte(),
10879                )
10880        })
10881        .cloned()
10882        .collect::<Vec<_>>();
10883    let first = candidates.first()?;
10884    if !candidates
10885        .iter()
10886        .all(|candidate| same_logical_symbol(first, candidate))
10887        || !candidates.iter().all(|candidate| {
10888            visibility.template_alias_arguments_preserve_target(
10889                analyzer, file, candidate, arguments, target,
10890            )
10891        })
10892    {
10893        return None;
10894    }
10895
10896    let mut resolved_components = namespace;
10897    resolved_components.push(identifier.clone());
10898    Some(LexicalTypeResolution::Resolved {
10899        unit: target.clone(),
10900        components: resolved_components,
10901        candidates,
10902    })
10903}
10904
10905fn resolve_type_node_lexically_for_target_without_visibility(
10906    node: Node<'_>,
10907    analyzer: &CppGraphSource<'_>,
10908    visibility: &VisibilityIndex<'_>,
10909    file: &ProjectFile,
10910    source: &str,
10911    target: &CodeUnit,
10912) -> LexicalTypeResolution {
10913    let Some((components, global)) = type_reference_components(node, source) else {
10914        return LexicalTypeResolution::Missing;
10915    };
10916    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10917        node,
10918        analyzer,
10919        visibility,
10920        file,
10921        source,
10922        true,
10923        recovered_macro_decorated_declarator_type(node)
10924            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10925        &orphaned_namespace_scopes(visibility, file),
10926    ) {
10927        LexicalScopeResolution::Resolved(scope) => scope,
10928        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10929        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10930    };
10931    visibility.resolve_type_components_lexically_for_target(
10932        analyzer,
10933        file,
10934        &components,
10935        global,
10936        &lexical_scope,
10937        target,
10938    )
10939}
10940
10941fn type_node_has_exact_target_identity_without_visibility(
10942    node: Node<'_>,
10943    analyzer: &CppGraphSource<'_>,
10944    visibility: &VisibilityIndex<'_>,
10945    file: &ProjectFile,
10946    source: &str,
10947    target: &CodeUnit,
10948) -> bool {
10949    let Some((components, global)) = type_reference_components(node, source) else {
10950        return false;
10951    };
10952    let LexicalScopeResolution::Resolved(lexical_scope) =
10953        enclosing_lexical_scope_components_with_unresolved_owner(
10954            node,
10955            analyzer,
10956            visibility,
10957            file,
10958            source,
10959            true,
10960            recovered_macro_decorated_declarator_type(node)
10961                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10962            &orphaned_namespace_scopes(visibility, file),
10963        )
10964    else {
10965        return false;
10966    };
10967    let target_name = cpp_name_for(target);
10968    lexical_component_tiers(&components, global, &lexical_scope)
10969        .any(|qualified| qualified.join("::") == target_name)
10970}
10971
10972pub fn resolve_using_enum_declaration_owner(
10973    node: Node<'_>,
10974    analyzer: &CppGraphSource<'_>,
10975    visibility: &VisibilityIndex<'_>,
10976    ordinary_type_imports: &OrdinaryTypeImportCell,
10977    file: &ProjectFile,
10978    source: &str,
10979) -> LexicalTypeResolution {
10980    let Some(type_node) = using_enum_declaration_type_node(node) else {
10981        return LexicalTypeResolution::Missing;
10982    };
10983    let mut components = Vec::new();
10984    if append_cpp_name_components(type_node, source, &mut components).is_none()
10985        || components.is_empty()
10986    {
10987        return LexicalTypeResolution::Missing;
10988    }
10989    resolve_type_components_lexically_at(
10990        type_node,
10991        &components,
10992        is_globally_qualified_cpp_name(type_node),
10993        analyzer,
10994        visibility,
10995        ordinary_type_imports,
10996        file,
10997        source,
10998    )
10999}
11000
11001pub fn resolve_ordinary_using_declaration_owner(
11002    node: Node<'_>,
11003    analyzer: &CppGraphSource<'_>,
11004    visibility: &VisibilityIndex<'_>,
11005    file: &ProjectFile,
11006    source: &str,
11007) -> LexicalTypeResolution {
11008    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
11009        return LexicalTypeResolution::Missing;
11010    };
11011    let mut components = Vec::new();
11012    if append_cpp_name_components(type_node, source, &mut components).is_none()
11013        || components.len() < 2
11014    {
11015        return LexicalTypeResolution::Missing;
11016    }
11017    let lexical_scope =
11018        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
11019            LexicalScopeResolution::Resolved(scope) => scope,
11020            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11021            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11022        };
11023    visibility.resolve_type_components_lexically(
11024        analyzer,
11025        file,
11026        &components,
11027        is_globally_qualified_cpp_name(type_node),
11028        &lexical_scope,
11029    )
11030}
11031
11032pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11033    (node.kind() == "using_declaration"
11034        && (0..node.child_count()).any(|index| {
11035            node.child(index)
11036                .is_some_and(|child| child.kind() == "enum")
11037        }))
11038    .then(|| node.named_child(0))
11039    .flatten()
11040}
11041
11042pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11043    (node.kind() == "using_declaration"
11044        && using_enum_declaration_type_node(node).is_none()
11045        && using_namespace_directive_name_node(node).is_none())
11046    .then(|| node.named_child(0))
11047    .flatten()
11048}
11049
11050/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
11051/// undefined namespace-sentinel macro as a declaration whose type is the
11052/// all-caps sentinel and whose qualified declarator starts with a pseudo
11053/// `using` scope. The real imported name remains a structured qualified
11054/// identifier under that declarator. Recover only this exact CST envelope so
11055/// ordinary macro-decorated variables are not treated as imports.
11056fn recovered_macro_using_declaration_type_node<'tree>(
11057    node: Node<'tree>,
11058    source: &str,
11059) -> Option<(Node<'tree>, bool)> {
11060    if node.kind() != "declaration" {
11061        return None;
11062    }
11063    let macro_type = node.child_by_field_name("type")?;
11064    if macro_type.kind() != "type_identifier"
11065        || !cpp_export_macro_token(node_text(macro_type, source))
11066    {
11067        return None;
11068    }
11069    let declarator = node.child_by_field_name("declarator")?;
11070    if declarator.kind() != "qualified_identifier" {
11071        return None;
11072    }
11073    let scope = declarator.child_by_field_name("scope")?;
11074    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
11075        return None;
11076    }
11077    let target = declarator.child_by_field_name("name")?;
11078    let mut components = Vec::new();
11079    append_cpp_name_components(target, source, &mut components)?;
11080    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
11081}
11082
11083fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11084    let is_directive = node.kind() == "using_directive"
11085        || (node.kind() == "using_declaration"
11086            && (0..node.child_count()).any(|index| {
11087                node.child(index)
11088                    .is_some_and(|child| child.kind() == "namespace")
11089            }));
11090    if !is_directive {
11091        return None;
11092    }
11093    node.child_by_field_name("name")
11094        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11095}
11096
11097fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11098    let mut current = node.parent();
11099    while let Some(parent) = current {
11100        if matches!(
11101            parent.kind(),
11102            "compound_statement"
11103                | "function_definition"
11104                | "lambda_expression"
11105                | "for_statement"
11106                | "while_statement"
11107                | "if_statement"
11108                | "class_specifier"
11109                | "struct_specifier"
11110                | "union_specifier"
11111        ) {
11112            return None;
11113        }
11114        current = parent.parent();
11115    }
11116    Some(enclosing_namespace_components(node, source))
11117}
11118
11119fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11120    let mut current = node.parent();
11121    while let Some(scope) = current {
11122        if matches!(
11123            scope.kind(),
11124            "compound_statement"
11125                | "declaration_list"
11126                | "field_declaration_list"
11127                | "translation_unit"
11128        ) {
11129            let mut depth = 0;
11130            let mut ancestor = scope.parent();
11131            while let Some(parent) = ancestor {
11132                depth += 1;
11133                ancestor = parent.parent();
11134            }
11135            return Some((
11136                scope.start_byte(),
11137                scope.end_byte(),
11138                depth,
11139                scope.kind() == "compound_statement",
11140            ));
11141        }
11142        current = scope.parent();
11143    }
11144    None
11145}
11146
11147/// Build the per-file structured using index for `file`.
11148///
11149/// The result is a pure function of the file's parsed content, which is what
11150/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11151/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11152/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11153pub fn build_source_using_index(
11154    cpp: &dyn CppSource,
11155    token: QueryToken<'_>,
11156    file: &ProjectFile,
11157) -> SourceUsingIndex {
11158    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11159        return SourceUsingIndex::default();
11160    };
11161    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11162}
11163
11164fn collect_source_using_index(
11165    cpp: &dyn CppSource,
11166    source_file: &ProjectFile,
11167    root: Node<'_>,
11168    source: &str,
11169) -> SourceUsingIndex {
11170    #[cfg(not(any(test, feature = "test-support")))]
11171    let _ = cpp;
11172    let mut index = SourceUsingIndex::default();
11173    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11174    let mut stack = vec![root];
11175    while let Some(node) = stack.pop() {
11176        let target = match node.kind() {
11177            "using_directive" | "using_declaration" => {
11178                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11179                    let mut namespace_components = Vec::new();
11180                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11181                        .map(|_| EffectiveUsingTarget::Namespace {
11182                            namespace_components,
11183                            global: is_globally_qualified_cpp_name(namespace_node),
11184                        })
11185                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11186                    let mut target_components = Vec::new();
11187                    (append_cpp_name_components(type_node, source, &mut target_components)
11188                        .is_some()
11189                        && target_components.len() >= 2)
11190                        .then(|| EffectiveUsingTarget::Ordinary {
11191                            name: target_components
11192                                .last()
11193                                .expect("ordinary using has a terminal component")
11194                                .clone(),
11195                            target_components,
11196                            global: is_globally_qualified_cpp_name(type_node),
11197                        })
11198                } else {
11199                    None
11200                }
11201            }
11202            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11203                |(type_node, global)| {
11204                    let mut target_components = Vec::new();
11205                    (append_cpp_name_components(type_node, source, &mut target_components)
11206                        .is_some()
11207                        && target_components.len() >= 2)
11208                        .then(|| EffectiveUsingTarget::Ordinary {
11209                            name: target_components
11210                                .last()
11211                                .expect("recovered ordinary using has a terminal component")
11212                                .clone(),
11213                            target_components,
11214                            global,
11215                        })
11216                },
11217            ),
11218            _ => None,
11219        };
11220        if let Some(target) = target {
11221            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11222            // searches from the root. The project index visits every AST node, but only these
11223            // structured using declarations need a guard environment. Keep the cheap target
11224            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11225            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11226            #[cfg(any(test, feature = "test-support"))]
11227            cpp.record_using_guard_context_inspection_for_test();
11228            let Some(required_guards) = binding_site_guards(node, source) else {
11229                let mut cursor = node.walk();
11230                stack.extend(node.children(&mut cursor));
11231                continue;
11232            };
11233            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11234                ordinary_using_scope(node)
11235            {
11236                let declaration_namespace = enclosing_namespace_components(node, source);
11237                let declaration_namespace = if declaration_namespace.is_empty() {
11238                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11239                        .unwrap_or(declaration_namespace)
11240                } else {
11241                    declaration_namespace
11242                };
11243                let namespace_scope = using_named_scope(node, source);
11244                let lexical_depth = declaration_namespace.len();
11245                let binding = OrdinaryTypeImport {
11246                    target,
11247                    source: source_file.clone(),
11248                    declaration_byte: node.end_byte(),
11249                    scope_start,
11250                    scope_end,
11251                    scope_depth,
11252                    block_scope,
11253                    lexical_depth,
11254                    declaration_namespace,
11255                    namespace_scope,
11256                    resolved_target_components: None,
11257                    required_guards,
11258                };
11259                match &binding.target {
11260                    EffectiveUsingTarget::Ordinary { name, .. } => index
11261                        .ordinary_by_name
11262                        .entry(name.clone())
11263                        .or_default()
11264                        .push(binding),
11265                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11266                }
11267            }
11268        }
11269        let mut cursor = node.walk();
11270        stack.extend(node.children(&mut cursor));
11271    }
11272    index
11273}
11274
11275struct OrphanedNamespaceEnvelope {
11276    body_end: usize,
11277    components: Vec<String>,
11278    class_names: HashSet<String>,
11279}
11280
11281/// Tree-sitter can terminate a namespace body at an object-like namespace
11282/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11283/// out-of-line definitions at translation-unit scope. A block-scoped using
11284/// declaration in one of those definitions still belongs to the namespace
11285/// selected by the malformed namespace envelope. Keep the envelope scan
11286/// source-local and reuse its structural ownership evidence for each using.
11287fn collect_orphaned_namespace_envelopes(
11288    root: Node<'_>,
11289    source: &str,
11290) -> Vec<OrphanedNamespaceEnvelope> {
11291    let mut envelopes = Vec::new();
11292    let mut stack = vec![root];
11293    while let Some(current) = stack.pop() {
11294        if current.kind() == "namespace_definition"
11295            && let Some(body) = current.child_by_field_name("body")
11296            && current.end_byte() == body.end_byte()
11297            && let Some(name) = current.child_by_field_name("name")
11298        {
11299            let mut components = enclosing_namespace_components(current, source);
11300            if append_cpp_name_components(name, source, &mut components).is_some()
11301                && !components.is_empty()
11302            {
11303                let mut class_names = HashSet::default();
11304                let mut body_stack = vec![body];
11305                while let Some(node) = body_stack.pop() {
11306                    if let Some(name) = orphaned_class_definition_name(node, source) {
11307                        class_names.insert(name);
11308                    }
11309                    let mut cursor = node.walk();
11310                    if node.kind() == "ERROR" {
11311                        body_stack.extend(node.children(&mut cursor));
11312                    } else {
11313                        body_stack.extend(node.named_children(&mut cursor));
11314                    }
11315                }
11316                envelopes.push(OrphanedNamespaceEnvelope {
11317                    body_end: body.end_byte(),
11318                    components,
11319                    class_names,
11320                });
11321            }
11322        }
11323        let mut cursor = current.walk();
11324        stack.extend(current.named_children(&mut cursor));
11325    }
11326    envelopes
11327}
11328
11329fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11330    if matches!(
11331        node.kind(),
11332        "class_specifier" | "struct_specifier" | "union_specifier"
11333    ) {
11334        let body = node.child_by_field_name("body")?;
11335        let name = node.child_by_field_name("name")?;
11336        return (!name.is_missing() && !body.is_missing())
11337            .then(|| node_text(name, source).to_string());
11338    }
11339    if node.kind() != "ERROR" {
11340        return None;
11341    }
11342
11343    // When an object-like namespace macro is parsed as a function definition,
11344    // tree-sitter can place the entire class declaration inside an ERROR node
11345    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11346    // fallback structural: accept only a named class-like keyword followed by
11347    // a real body, never an arbitrary identifier mentioned in the envelope.
11348    for index in 0..node.child_count() {
11349        let Some(keyword) = node.child(index) else {
11350            continue;
11351        };
11352        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11353            continue;
11354        }
11355        let mut name = None;
11356        for next_index in (index + 1)..node.child_count() {
11357            let Some(next) = node.child(next_index) else {
11358                continue;
11359            };
11360            if next.kind() == ";" {
11361                break;
11362            }
11363            if next.kind() == "{" {
11364                return name
11365                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11366                    .map(|name_node| node_text(name_node, source).to_string());
11367            }
11368            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11369                name = Some(next);
11370            }
11371        }
11372    }
11373    None
11374}
11375
11376fn recovered_orphaned_namespace_components(
11377    node: Node<'_>,
11378    source: &str,
11379    envelopes: &[OrphanedNamespaceEnvelope],
11380) -> Option<Vec<String>> {
11381    let owner_name = orphaned_using_owner_name(node, source)?;
11382    envelopes
11383        .iter()
11384        .filter(|envelope| {
11385            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11386        })
11387        .max_by_key(|envelope| envelope.body_end)
11388        .map(|envelope| envelope.components.clone())
11389}
11390
11391fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11392    let function = std::iter::successors(node.parent(), |current| current.parent())
11393        .find(|current| current.kind() == "function_definition")?;
11394    let owner = function_definition_owner_lookup_node(function)?;
11395    let scope = owner.child_by_field_name("scope")?;
11396    let mut components = Vec::new();
11397    append_cpp_name_components(scope, source, &mut components)?;
11398    // A qualified out-of-line member definition already carries its namespace
11399    // in the declarator scope (for example `foo::Widget::run`). The recovery
11400    // path is only for parser-orphaned top-level members whose owner scope
11401    // collapsed to the bare class name; requiring that shape prevents an
11402    // earlier, unrelated namespace/class from leaking into a global function's
11403    // using-directive lookup.
11404    if components.len() != 1 {
11405        return None;
11406    }
11407    components.pop()
11408}
11409
11410fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11411    let started = Instant::now();
11412    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11413    let source_files = visibility.all_visible_source_files();
11414    if report_stats {
11415        eprintln!(
11416            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11417            source_files.len()
11418        );
11419    }
11420    let mut project = ProjectUsingIndex::default();
11421    let mut ordinary_bindings = 0usize;
11422    for source_file in &source_files {
11423        // The per-file index is memoized on the analyzer, so assembling the
11424        // project index for a fresh `VisibilityIndex` copies bindings instead
11425        // of re-walking each file's AST (#1927).
11426        let source_index = visibility
11427            .cpp()
11428            .source_using_index(visibility.token(), source_file);
11429        for (name, bindings) in &source_index.ordinary_by_name {
11430            ordinary_bindings += bindings.len();
11431            project
11432                .ordinary_by_name
11433                .entry(name.clone())
11434                .or_default()
11435                .extend(bindings.iter().cloned());
11436        }
11437        project
11438            .directives
11439            .extend(source_index.directives.iter().cloned());
11440    }
11441    if report_stats {
11442        eprintln!(
11443            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11444            source_files.len(),
11445            project.ordinary_by_name.len(),
11446            ordinary_bindings,
11447            project.directives.len(),
11448            started.elapsed().as_millis(),
11449        );
11450    }
11451    project
11452}
11453
11454fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11455    visibility.project_using_index(|| build_project_using_index(visibility))
11456}
11457
11458/// Build the immutable project-wide using index before a parallel file scan.
11459///
11460/// Keeping the `OnceLock` publication here avoids making every scanner carry
11461/// an eager index, while callers that are about to fan out can prevent one
11462/// worker from doing the whole build as its peers wait on the lock.
11463pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11464    let _ = project_using_index(visibility);
11465}
11466
11467fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11468    let (components, global) = match &binding.target {
11469        EffectiveUsingTarget::Ordinary {
11470            target_components,
11471            global,
11472            ..
11473        } => (target_components, *global),
11474        EffectiveUsingTarget::Namespace {
11475            namespace_components,
11476            global,
11477        } => (namespace_components, *global),
11478    };
11479    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11480}
11481
11482fn using_binding_target_components_for_name(
11483    binding: &OrdinaryTypeImport,
11484    project: &ProjectUsingIndex,
11485    visibility: &VisibilityIndex<'_>,
11486    file: &ProjectFile,
11487    name: &str,
11488) -> Option<Vec<String>> {
11489    // Built once per call rather than per candidate: the filter runs over every
11490    // visible identifier of `name`, and the source is the same object each time.
11491    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
11492    let visible_candidates = visibility
11493        .visible_identifier_candidates(file, name)
11494        .filter(|candidate| {
11495            candidate.is_class()
11496                || declared_type_alias(&cpp_source, candidate)
11497                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
11498        })
11499        .collect::<Vec<_>>();
11500    if visible_candidates.is_empty() {
11501        return None;
11502    }
11503    match &binding.target {
11504        EffectiveUsingTarget::Ordinary {
11505            name: imported_name,
11506            ..
11507        } if imported_name == name => {
11508            effective_using_target_tiers(binding)
11509                .into_iter()
11510                .find(|qualified| {
11511                    let qualified_name = qualified.join("::");
11512                    visible_candidates
11513                        .iter()
11514                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
11515                })
11516        }
11517        EffectiveUsingTarget::Namespace { .. } => {
11518            visibility.note_using_namespace_lookup_for_test();
11519            let target_tiers = effective_using_target_tiers(binding);
11520            let resolved = target_tiers
11521                .iter()
11522                .find(|namespace_components| {
11523                    let namespace = namespace_components.join("::");
11524                    visible_candidates.iter().any(|candidate| {
11525                        visibility.note_using_name_candidate_inspection_for_test();
11526                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
11527                            candidate_namespace == namespace
11528                                || candidate_namespace.starts_with(&format!("{namespace}::"))
11529                        })
11530                    }) || project.directives.iter().any(|candidate| {
11531                        candidate.namespace_scope.as_deref()
11532                            == Some(namespace_components.as_slice())
11533                    }) || project
11534                        .ordinary_by_name
11535                        .values()
11536                        .flatten()
11537                        .any(|candidate| {
11538                            candidate.namespace_scope.as_deref()
11539                                == Some(namespace_components.as_slice())
11540                        })
11541                })
11542                .cloned();
11543            resolved.or_else(|| {
11544                // A sole lexical namespace tier is itself enough to retain the
11545                // directive. Candidate identity is resolved later, where
11546                // target guidance and macro-expanded owner names are available.
11547                // Dropping it here makes an unrelated same-terminal type hide
11548                // the actual namespace member before lookup can compare owners.
11549                (target_tiers.len() == 1)
11550                    .then(|| target_tiers.into_iter().next())
11551                    .flatten()
11552            })
11553        }
11554        EffectiveUsingTarget::Ordinary { .. } => None,
11555    }
11556}
11557
11558fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
11559    let start = activation.checked_sub(1)?;
11560    let mut node = root.descendant_for_byte_range(start, activation)?;
11561    while node.kind() != "preproc_include" {
11562        node = node.parent()?;
11563    }
11564    Some(node)
11565}
11566
11567fn project_using_bindings(
11568    binding: OrdinaryTypeImport,
11569    visibility: &VisibilityIndex<'_>,
11570    file: &ProjectFile,
11571    root: Node<'_>,
11572    source: &str,
11573) -> Vec<OrdinaryTypeImport> {
11574    if binding.source == *file {
11575        return vec![binding];
11576    }
11577    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
11578        return Vec::new();
11579    }
11580    visibility.note_using_donor_activation_for_test();
11581    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
11582        return Vec::new();
11583    };
11584    let projections = visibility
11585        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
11586        .map_or_else(
11587            || {
11588                visibility.conditional_include_projections_for_source(
11589                    file,
11590                    prepared.as_ref(),
11591                    &binding.source,
11592                )
11593            },
11594            |activation_byte| {
11595                Arc::from([ConditionalIncludeProjection {
11596                    activation_byte,
11597                    required_guards: HashSet::default(),
11598                    partial_guards: HashSet::default(),
11599                }])
11600            },
11601        );
11602    projections
11603        .iter()
11604        .cloned()
11605        .filter_map(|projection| {
11606            let required_guards =
11607                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
11608            let mut projected = binding.clone();
11609            projected.required_guards = required_guards;
11610            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
11611        })
11612        .collect()
11613}
11614
11615fn project_using_binding_at_activation(
11616    mut binding: OrdinaryTypeImport,
11617    activation: usize,
11618    root: Node<'_>,
11619    source: &str,
11620) -> Option<OrdinaryTypeImport> {
11621    let include = include_node_for_activation(root, activation)?;
11622    let include_namespace = enclosing_namespace_components(include, source);
11623    let mut declaration_namespace = include_namespace.clone();
11624    declaration_namespace.extend(binding.declaration_namespace);
11625    binding.declaration_namespace = declaration_namespace;
11626    binding.declaration_byte = activation;
11627    if let Some(prefix) = using_named_scope(include, source) {
11628        let mut projected = prefix;
11629        projected.extend(binding.namespace_scope.take().unwrap_or_default());
11630        binding.scope_depth = projected.len();
11631        binding.block_scope = false;
11632        binding.lexical_depth = projected.len();
11633        binding.namespace_scope = Some(projected);
11634        binding.scope_start = 0;
11635        binding.scope_end = usize::MAX;
11636        Some(binding)
11637    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
11638        binding.namespace_scope = None;
11639        binding.scope_start = start;
11640        binding.scope_end = end;
11641        binding.scope_depth = depth;
11642        binding.block_scope = block_scope;
11643        binding.lexical_depth = include_namespace.len();
11644        Some(binding)
11645    } else {
11646        None
11647    }
11648}
11649
11650/// `node` may be any node of `file`'s tree; the projection reaches the tree
11651/// root itself when it needs one. `Node::parent` re-descends from the root on
11652/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
11653/// call site cost a near-full-AST scan per reference on a large flat file
11654/// (#1927); a name with no candidate bindings never pays for it.
11655pub fn effective_using_bindings_for_name(
11656    visibility: &VisibilityIndex<'_>,
11657    imports: &OrdinaryTypeImportCell,
11658    file: &ProjectFile,
11659    node: Node<'_>,
11660    source: &str,
11661    name: &str,
11662) -> Arc<[OrdinaryTypeImport]> {
11663    imports
11664        .projection_cell(name)
11665        .get_or_init(|| {
11666            let project = project_using_index(visibility);
11667            let name_bindings = project.ordinary_by_name.get(name);
11668            if name_bindings.is_none() && project.directives.is_empty() {
11669                return Arc::from(Vec::new());
11670            }
11671            let root = root_node(node);
11672            let mut projected = Vec::new();
11673            for binding in name_bindings
11674                .into_iter()
11675                .flatten()
11676                .chain(project.directives.iter())
11677            {
11678                if !visibility.source_is_visible(file, &binding.source) {
11679                    continue;
11680                }
11681                let target_components = using_binding_target_components_for_name(
11682                    binding, project, visibility, file, name,
11683                )
11684                .or_else(|| match &binding.target {
11685                    EffectiveUsingTarget::Ordinary {
11686                        name: imported_name,
11687                        target_components,
11688                        ..
11689                    } if imported_name == name => Some(target_components.clone()),
11690                    EffectiveUsingTarget::Ordinary { .. }
11691                    | EffectiveUsingTarget::Namespace { .. } => None,
11692                });
11693                let Some(target_components) = target_components else {
11694                    continue;
11695                };
11696                let mut binding = binding.clone();
11697                binding.resolved_target_components = Some(target_components);
11698                projected.extend(project_using_bindings(
11699                    binding, visibility, file, root, source,
11700                ));
11701            }
11702            Arc::from(projected)
11703        })
11704        .clone()
11705}
11706
11707pub fn initialized_ordinary_type_imports(
11708    root: Node<'_>,
11709    analyzer: &CppGraphSource<'_>,
11710    visibility: &VisibilityIndex<'_>,
11711    file: &ProjectFile,
11712    source: &str,
11713) -> OrdinaryTypeImportCell {
11714    let cell = visibility.ordinary_type_import_cell(file);
11715    let _ = (root, analyzer, source);
11716    cell
11717}
11718
11719/// `reference_guards` is the reference node's guard environment, computed once
11720/// by the caller and shared across every binding: recomputing it per binding
11721/// repeated a full ancestor climb whose every `Node::parent` step re-descends
11722/// from the root (#1927).
11723fn effective_using_binding_active(
11724    binding: &OrdinaryTypeImport,
11725    node: Node<'_>,
11726    lexical_scope: &[String],
11727    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11728    visibility: &VisibilityIndex<'_>,
11729    file: &ProjectFile,
11730) -> bool {
11731    effective_using_binding_guards_active(
11732        binding,
11733        node.start_byte(),
11734        reference_guards,
11735        visibility,
11736        file,
11737    ) && binding.namespace_scope.as_ref().map_or_else(
11738        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11739        |namespace| lexical_scope.starts_with(namespace),
11740    )
11741}
11742
11743fn effective_using_binding_guards_active(
11744    binding: &OrdinaryTypeImport,
11745    reference_byte: usize,
11746    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11747    visibility: &VisibilityIndex<'_>,
11748    file: &ProjectFile,
11749) -> bool {
11750    binding.declaration_byte <= reference_byte
11751        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
11752        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11753}
11754
11755fn effective_using_binding_guards_compatible(
11756    binding: &OrdinaryTypeImport,
11757    reference_byte: usize,
11758    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11759    visibility: &VisibilityIndex<'_>,
11760    file: &ProjectFile,
11761) -> bool {
11762    binding.source != *file
11763        && !binding.required_guards.is_empty()
11764        && binding.declaration_byte <= reference_byte
11765        && reference_guards.is_some_and(|active| {
11766            !binding.required_guards.is_subset(active)
11767                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
11768        })
11769        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11770}
11771
11772fn effective_using_binding_guards_stable_between(
11773    binding: &OrdinaryTypeImport,
11774    reference_byte: usize,
11775    visibility: &VisibilityIndex<'_>,
11776    file: &ProjectFile,
11777) -> bool {
11778    visibility.preprocessor_guards_stable_between(
11779        file,
11780        binding.declaration_byte,
11781        reference_byte,
11782        &binding.required_guards,
11783    )
11784}
11785
11786#[allow(clippy::too_many_arguments)]
11787fn binding_type_candidates(
11788    binding: &OrdinaryTypeImport,
11789    active_bindings: &[&OrdinaryTypeImport],
11790    analyzer: &CppGraphSource<'_>,
11791    visibility: &VisibilityIndex<'_>,
11792    file: &ProjectFile,
11793    name: &str,
11794    direct_target: Option<&CodeUnit>,
11795    reference_byte: usize,
11796) -> Vec<(CodeUnit, Vec<String>)> {
11797    ordinary_binding_lookup_targets(binding, active_bindings, name)
11798        .into_iter()
11799        .flat_map(|target| {
11800            let mut candidates = visibility
11801                .visible_identifier_candidates(file, name)
11802                .filter(|candidate| {
11803                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
11804                        && type_candidate_matches_lookup_components(
11805                            analyzer,
11806                            visibility,
11807                            file,
11808                            candidate,
11809                            reference_byte,
11810                            &target,
11811                        )
11812                })
11813                .cloned()
11814                .collect::<Vec<_>>();
11815            if candidates.is_empty()
11816                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11817                && let Some(target_unit) = direct_target
11818            {
11819                let expanded_target_name = macro_expanded_cpp_name_components(
11820                    visibility,
11821                    file,
11822                    target_unit,
11823                    reference_byte,
11824                );
11825                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
11826                    && expanded_target_name == target
11827                    && visibility.external_type_candidate_visible_at(
11828                        file,
11829                        target_unit,
11830                        reference_byte,
11831                    )
11832                {
11833                    candidates.push(target_unit.clone());
11834                }
11835            }
11836            if candidates.is_empty()
11837                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11838                && let Some(target_unit) = direct_target
11839            {
11840                let visible_types = visibility
11841                    .visible_identifier_candidates(file, name)
11842                    .filter(|candidate| {
11843                        candidate.is_class() || declared_type_alias(analyzer, candidate)
11844                    })
11845                    .collect::<Vec<_>>();
11846                let uniquely_names_target = !visible_types.is_empty()
11847                    && visible_types
11848                        .iter()
11849                        .all(|candidate| same_visible_symbol(candidate, target_unit));
11850                if uniquely_names_target {
11851                    candidates.extend(visible_types.into_iter().cloned());
11852                }
11853            }
11854            candidates
11855                .into_iter()
11856                .map(move |candidate| (candidate, target.clone()))
11857        })
11858        .collect()
11859}
11860
11861fn macro_expanded_cpp_name_components(
11862    visibility: &VisibilityIndex<'_>,
11863    file: &ProjectFile,
11864    unit: &CodeUnit,
11865    reference_byte: usize,
11866) -> Vec<String> {
11867    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11868        brokk_bifrost_core::analyzer::Language::Cpp,
11869        &cpp_name_for(unit),
11870    )
11871    .into_iter()
11872    .flat_map(|component| {
11873        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11874    })
11875    .collect()
11876}
11877
11878fn macro_expanded_cpp_name_component(
11879    visibility: &VisibilityIndex<'_>,
11880    file: &ProjectFile,
11881    component: String,
11882    reference_byte: usize,
11883) -> Vec<String> {
11884    let Some(replacement) =
11885        visibility.object_macro_replacement_at(file, &component, reference_byte)
11886    else {
11887        return vec![component];
11888    };
11889    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11890        brokk_bifrost_core::analyzer::Language::Cpp,
11891        &replacement,
11892    );
11893    if expanded.is_empty() {
11894        vec![component]
11895    } else {
11896        expanded
11897    }
11898}
11899
11900/// Whether a visible type has the requested qualified spelling.
11901///
11902/// Members of an inline namespace are also members of its enclosing namespace,
11903/// so an ordinary using-declaration may legally omit the inline component. The
11904/// stored FQ name retains that component to keep declarations distinct. Recover
11905/// the omitted spellings from the candidate declaration's namespace ancestors,
11906/// using CST `inline` tokens rather than guessing from names.
11907fn type_candidate_matches_lookup_components(
11908    analyzer: &CppGraphSource<'_>,
11909    visibility: &VisibilityIndex<'_>,
11910    file: &ProjectFile,
11911    candidate: &CodeUnit,
11912    reference_byte: usize,
11913    target: &[String],
11914) -> bool {
11915    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11916    if expanded == target {
11917        return true;
11918    }
11919    let Some(cpp) = analyzer.cpp else {
11920        return false;
11921    };
11922    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11923        return false;
11924    };
11925    let root = prepared.tree().root_node();
11926    for range in analyzer.ranges(candidate) {
11927        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11928        else {
11929            continue;
11930        };
11931        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11932        loop {
11933            if current.kind() == "namespace_definition"
11934                && let Some(name) = current.child_by_field_name("name")
11935            {
11936                let mut components = Vec::new();
11937                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11938                    && !components.is_empty()
11939                {
11940                    let inline = (0..current.child_count())
11941                        .filter_map(|index| current.child(index))
11942                        .any(|child| !child.is_named() && child.kind() == "inline");
11943                    namespaces.push((components, inline));
11944                }
11945            }
11946            let Some(parent) = current.parent() else {
11947                break;
11948            };
11949            current = parent;
11950        }
11951        namespaces.reverse();
11952        let mut namespace_components = Vec::new();
11953        let mut inline_indexes = HashSet::default();
11954        for (components, inline) in namespaces {
11955            for component in components {
11956                let expanded_component =
11957                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11958                if inline {
11959                    inline_indexes.extend(
11960                        namespace_components.len()
11961                            ..namespace_components.len() + expanded_component.len(),
11962                    );
11963                }
11964                namespace_components.extend(expanded_component);
11965            }
11966        }
11967        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11968            continue;
11969        }
11970        // Each inline namespace component can be present or elided. Compare
11971        // those alternatives as a small dynamic program instead of generating
11972        // every subset of a deeply nested inline-namespace chain.
11973        let mut reachable = vec![false; target.len() + 1];
11974        reachable[0] = true;
11975        for (index, component) in expanded.iter().enumerate() {
11976            let mut next = vec![false; target.len() + 1];
11977            for (target_index, reached) in reachable.iter().copied().enumerate() {
11978                if !reached {
11979                    continue;
11980                }
11981                if inline_indexes.contains(&index) {
11982                    next[target_index] = true;
11983                }
11984                if target
11985                    .get(target_index)
11986                    .is_some_and(|target_component| target_component == component)
11987                {
11988                    next[target_index + 1] = true;
11989                }
11990            }
11991            reachable = next;
11992        }
11993        if reachable[target.len()] {
11994            return true;
11995        }
11996    }
11997    false
11998}
11999
12000#[allow(clippy::too_many_arguments)]
12001fn resolved_type_import(
12002    candidates: Vec<(CodeUnit, Vec<String>)>,
12003    lexical_depth: usize,
12004    is_direct: bool,
12005    analyzer: &CppGraphSource<'_>,
12006    visibility: &VisibilityIndex<'_>,
12007    file: &ProjectFile,
12008    direct_target: Option<&CodeUnit>,
12009) -> OrdinaryTypeImportResolution {
12010    let logical = deduplicate_type_import_candidates(candidates);
12011    let selected = match logical.as_slice() {
12012        [] => return OrdinaryTypeImportResolution::Missing,
12013        [only] => only,
12014        // Several declarations of one FQN in one file are configuration
12015        // spellings of one entity, not competing types (#1845): the imported
12016        // name is unambiguous, only the branch that supplies it depends on the
12017        // build.
12018        several => {
12019            let units = several
12020                .iter()
12021                .map(|(unit, _)| unit)
12022                .collect::<Vec<&CodeUnit>>();
12023            let Some(spelling) = direct_target.and_then(|target| {
12024                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
12025            }) else {
12026                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
12027            };
12028            several
12029                .iter()
12030                .find(|(unit, _)| same_symbol(unit, spelling))
12031                .expect("the selected spelling is one of the imported candidates")
12032        }
12033    };
12034    OrdinaryTypeImportResolution::Resolved {
12035        target: selected.0.clone(),
12036        target_components: selected.1.clone(),
12037        lexical_depth,
12038        is_direct,
12039    }
12040}
12041
12042#[allow(clippy::too_many_arguments)]
12043fn ordinary_type_import_resolution(
12044    node: Node<'_>,
12045    components: &[String],
12046    global: bool,
12047    analyzer: &CppGraphSource<'_>,
12048    visibility: &VisibilityIndex<'_>,
12049    imports: &OrdinaryTypeImportCell,
12050    file: &ProjectFile,
12051    source: &str,
12052    lexical_scope: &[String],
12053    direct_target: Option<&CodeUnit>,
12054) -> OrdinaryTypeImportResolution {
12055    if global || components.len() != 1 {
12056        return OrdinaryTypeImportResolution::Missing;
12057    }
12058    let name = &components[0];
12059    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12060    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
12061    // step re-descends from the root (#1927). A name with no bindings needs
12062    // none of it, and one environment serves every binding of the reference.
12063    if bindings.is_empty() {
12064        return OrdinaryTypeImportResolution::Missing;
12065    }
12066    let reference_guards = preprocessor_guard_environment(node, source);
12067    let active = bindings
12068        .iter()
12069        .filter(|binding| {
12070            effective_using_binding_active(
12071                binding,
12072                node,
12073                lexical_scope,
12074                reference_guards.as_ref(),
12075                visibility,
12076                file,
12077            )
12078        })
12079        .collect::<Vec<_>>();
12080    let transitive = bindings
12081        .iter()
12082        .filter(|binding| {
12083            effective_using_binding_guards_active(
12084                binding,
12085                node.start_byte(),
12086                reference_guards.as_ref(),
12087                visibility,
12088                file,
12089            ) && (binding.namespace_scope.is_some()
12090                || (binding.scope_start <= node.start_byte()
12091                    && node.end_byte() <= binding.scope_end))
12092        })
12093        .collect::<Vec<_>>();
12094    ordinary_type_import_resolution_for_bindings(
12095        node,
12096        name,
12097        analyzer,
12098        visibility,
12099        file,
12100        lexical_scope,
12101        direct_target,
12102        &active,
12103        &transitive,
12104    )
12105}
12106
12107#[allow(clippy::too_many_arguments)]
12108fn compatible_foreign_type_import_resolution(
12109    node: Node<'_>,
12110    components: &[String],
12111    global: bool,
12112    analyzer: &CppGraphSource<'_>,
12113    visibility: &VisibilityIndex<'_>,
12114    imports: &OrdinaryTypeImportCell,
12115    file: &ProjectFile,
12116    source: &str,
12117    lexical_scope: &[String],
12118    direct_target: Option<&CodeUnit>,
12119) -> OrdinaryTypeImportResolution {
12120    if global || components.len() != 1 {
12121        return OrdinaryTypeImportResolution::Missing;
12122    }
12123    let name = &components[0];
12124    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12125    if bindings.is_empty() {
12126        return OrdinaryTypeImportResolution::Missing;
12127    }
12128    let reference_guards = preprocessor_guard_environment(node, source);
12129    let compatible = bindings
12130        .iter()
12131        .filter(|binding| {
12132            effective_using_binding_guards_compatible(
12133                binding,
12134                node.start_byte(),
12135                reference_guards.as_ref(),
12136                visibility,
12137                file,
12138            ) && binding.namespace_scope.as_ref().map_or_else(
12139                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12140                |namespace| lexical_scope.starts_with(namespace),
12141            )
12142        })
12143        .collect::<Vec<_>>();
12144    let transitive = bindings
12145        .iter()
12146        .filter(|binding| {
12147            effective_using_binding_guards_compatible(
12148                binding,
12149                node.start_byte(),
12150                reference_guards.as_ref(),
12151                visibility,
12152                file,
12153            ) && (binding.namespace_scope.is_some()
12154                || (binding.scope_start <= node.start_byte()
12155                    && node.end_byte() <= binding.scope_end))
12156        })
12157        .collect::<Vec<_>>();
12158    ordinary_type_import_resolution_for_bindings(
12159        node,
12160        name,
12161        analyzer,
12162        visibility,
12163        file,
12164        lexical_scope,
12165        direct_target,
12166        &compatible,
12167        &transitive,
12168    )
12169}
12170
12171#[allow(clippy::too_many_arguments)]
12172fn ordinary_type_import_resolution_for_bindings(
12173    node: Node<'_>,
12174    name: &str,
12175    analyzer: &CppGraphSource<'_>,
12176    visibility: &VisibilityIndex<'_>,
12177    file: &ProjectFile,
12178    lexical_scope: &[String],
12179    direct_target: Option<&CodeUnit>,
12180    active: &[&OrdinaryTypeImport],
12181    transitive: &[&OrdinaryTypeImport],
12182) -> OrdinaryTypeImportResolution {
12183    let mut concrete_depths = active
12184        .iter()
12185        .filter(|binding| binding.namespace_scope.is_none())
12186        .map(|binding| binding.scope_depth)
12187        .collect::<Vec<_>>();
12188    concrete_depths.sort_unstable();
12189    concrete_depths.dedup();
12190    for depth in concrete_depths.into_iter().rev() {
12191        let at_tier = active
12192            .iter()
12193            .copied()
12194            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12195        let direct = at_tier
12196            .clone()
12197            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12198            .flat_map(|binding| {
12199                binding_type_candidates(
12200                    binding,
12201                    transitive,
12202                    analyzer,
12203                    visibility,
12204                    file,
12205                    name,
12206                    direct_target,
12207                    node.start_byte(),
12208                )
12209            })
12210            .collect::<Vec<_>>();
12211        if !direct.is_empty() {
12212            return resolved_type_import(
12213                direct,
12214                lexical_scope.len(),
12215                true,
12216                analyzer,
12217                visibility,
12218                file,
12219                direct_target,
12220            );
12221        }
12222        let directives = at_tier
12223            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12224            .flat_map(|binding| {
12225                binding_type_candidates(
12226                    binding,
12227                    transitive,
12228                    analyzer,
12229                    visibility,
12230                    file,
12231                    name,
12232                    direct_target,
12233                    node.start_byte(),
12234                )
12235            })
12236            .collect::<Vec<_>>();
12237        if !directives.is_empty() {
12238            return resolved_type_import(
12239                directives,
12240                lexical_scope.len(),
12241                false,
12242                analyzer,
12243                visibility,
12244                file,
12245                direct_target,
12246            );
12247        }
12248    }
12249    for prefix_len in (0..=lexical_scope.len()).rev() {
12250        let tier = &lexical_scope[..prefix_len];
12251        let at_tier = active
12252            .iter()
12253            .copied()
12254            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12255        let direct = at_tier
12256            .clone()
12257            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12258            .flat_map(|binding| {
12259                binding_type_candidates(
12260                    binding,
12261                    transitive,
12262                    analyzer,
12263                    visibility,
12264                    file,
12265                    name,
12266                    direct_target,
12267                    node.start_byte(),
12268                )
12269            })
12270            .collect::<Vec<_>>();
12271        if !direct.is_empty() {
12272            return resolved_type_import(
12273                direct,
12274                prefix_len,
12275                true,
12276                analyzer,
12277                visibility,
12278                file,
12279                direct_target,
12280            );
12281        }
12282        let directives = at_tier
12283            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12284            .flat_map(|binding| {
12285                binding_type_candidates(
12286                    binding,
12287                    transitive,
12288                    analyzer,
12289                    visibility,
12290                    file,
12291                    name,
12292                    direct_target,
12293                    node.start_byte(),
12294                )
12295            })
12296            .collect::<Vec<_>>();
12297        if !directives.is_empty() {
12298            return resolved_type_import(
12299                directives,
12300                prefix_len,
12301                false,
12302                analyzer,
12303                visibility,
12304                file,
12305                direct_target,
12306            );
12307        }
12308    }
12309    OrdinaryTypeImportResolution::Missing
12310}
12311
12312#[allow(clippy::too_many_arguments)]
12313pub fn resolve_type_components_lexically_at(
12314    node: Node<'_>,
12315    components: &[String],
12316    global: bool,
12317    analyzer: &CppGraphSource<'_>,
12318    visibility: &VisibilityIndex<'_>,
12319    ordinary_type_imports: &OrdinaryTypeImportCell,
12320    file: &ProjectFile,
12321    source: &str,
12322) -> LexicalTypeResolution {
12323    resolve_type_components_lexically_at_inner(
12324        node,
12325        components,
12326        global,
12327        analyzer,
12328        visibility,
12329        ordinary_type_imports,
12330        file,
12331        source,
12332        None,
12333        false,
12334        false,
12335        false,
12336        None,
12337    )
12338}
12339
12340/// Resolve a type at its lexical reference site while retaining the identity
12341/// of an alias that C++ lookup selects.
12342///
12343/// Forward navigation uses the selected spelling as its destination, whereas
12344/// graph attribution normally canonicalizes an alias to its target. Both
12345/// surfaces must still apply the same ordinary using-declarations, declaration
12346/// order, guard state, and lexical-depth precedence.
12347#[allow(clippy::too_many_arguments)]
12348pub fn resolve_type_components_lexically_at_preserving_alias(
12349    node: Node<'_>,
12350    components: &[String],
12351    global: bool,
12352    analyzer: &CppGraphSource<'_>,
12353    visibility: &VisibilityIndex<'_>,
12354    file: &ProjectFile,
12355    source: &str,
12356) -> LexicalTypeResolution {
12357    let ordinary_type_imports =
12358        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12359    resolve_type_components_lexically_at_inner(
12360        node,
12361        components,
12362        global,
12363        analyzer,
12364        visibility,
12365        &ordinary_type_imports,
12366        file,
12367        source,
12368        None,
12369        false,
12370        true,
12371        true,
12372        None,
12373    )
12374}
12375
12376#[allow(clippy::too_many_arguments)]
12377fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12378    node: Node<'_>,
12379    components: &[String],
12380    global: bool,
12381    analyzer: &CppGraphSource<'_>,
12382    visibility: &VisibilityIndex<'_>,
12383    ordinary_type_imports: &OrdinaryTypeImportCell,
12384    file: &ProjectFile,
12385    source: &str,
12386    scope_cache: Option<&LexicalScopeCache>,
12387) -> LexicalTypeResolution {
12388    resolve_type_components_lexically_at_inner(
12389        node,
12390        components,
12391        global,
12392        analyzer,
12393        visibility,
12394        ordinary_type_imports,
12395        file,
12396        source,
12397        None,
12398        false,
12399        true,
12400        false,
12401        scope_cache,
12402    )
12403}
12404
12405#[allow(clippy::too_many_arguments)]
12406fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12407    node: Node<'_>,
12408    components: &[String],
12409    global: bool,
12410    analyzer: &CppGraphSource<'_>,
12411    visibility: &VisibilityIndex<'_>,
12412    ordinary_type_imports: &OrdinaryTypeImportCell,
12413    file: &ProjectFile,
12414    source: &str,
12415    target: &CodeUnit,
12416    apply_structured_prefilter: bool,
12417    scope_cache: Option<&LexicalScopeCache>,
12418) -> LexicalTypeResolution {
12419    resolve_type_components_lexically_at_inner(
12420        node,
12421        components,
12422        global,
12423        analyzer,
12424        visibility,
12425        ordinary_type_imports,
12426        file,
12427        source,
12428        Some(target),
12429        apply_structured_prefilter,
12430        false,
12431        false,
12432        scope_cache,
12433    )
12434}
12435
12436#[allow(clippy::too_many_arguments)]
12437fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12438    node: Node<'_>,
12439    components: &[String],
12440    global: bool,
12441    analyzer: &CppGraphSource<'_>,
12442    visibility: &VisibilityIndex<'_>,
12443    ordinary_type_imports: &OrdinaryTypeImportCell,
12444    file: &ProjectFile,
12445    source: &str,
12446    recovered_scope: &[String],
12447) -> LexicalTypeResolution {
12448    resolve_type_components_in_authoritative_scope(
12449        node,
12450        components,
12451        global,
12452        analyzer,
12453        visibility,
12454        ordinary_type_imports,
12455        file,
12456        source,
12457        None,
12458        false,
12459        true,
12460        false,
12461        recovered_scope.to_vec(),
12462    )
12463}
12464
12465#[allow(clippy::too_many_arguments)]
12466fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12467    node: Node<'_>,
12468    components: &[String],
12469    global: bool,
12470    analyzer: &CppGraphSource<'_>,
12471    visibility: &VisibilityIndex<'_>,
12472    ordinary_type_imports: &OrdinaryTypeImportCell,
12473    file: &ProjectFile,
12474    source: &str,
12475    target: &CodeUnit,
12476    apply_structured_prefilter: bool,
12477    recovered_scope: &[String],
12478) -> LexicalTypeResolution {
12479    resolve_type_components_in_authoritative_scope(
12480        node,
12481        components,
12482        global,
12483        analyzer,
12484        visibility,
12485        ordinary_type_imports,
12486        file,
12487        source,
12488        Some(target),
12489        apply_structured_prefilter,
12490        false,
12491        false,
12492        recovered_scope.to_vec(),
12493    )
12494}
12495
12496#[allow(clippy::too_many_arguments)]
12497fn resolve_type_components_lexically_at_inner(
12498    node: Node<'_>,
12499    components: &[String],
12500    global: bool,
12501    analyzer: &CppGraphSource<'_>,
12502    visibility: &VisibilityIndex<'_>,
12503    ordinary_type_imports: &OrdinaryTypeImportCell,
12504    file: &ProjectFile,
12505    source: &str,
12506    direct_target: Option<&CodeUnit>,
12507    apply_structured_prefilter: bool,
12508    preserve_alias: bool,
12509    allow_compatible_foreign_import: bool,
12510    scope_cache: Option<&LexicalScopeCache>,
12511) -> LexicalTypeResolution {
12512    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12513    let lexical_scope_started = Instant::now();
12514    if report_stats {
12515        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
12516    }
12517    let lexical_scope = if global {
12518        Vec::new()
12519    } else {
12520        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
12521            node,
12522            analyzer,
12523            visibility,
12524            file,
12525            source,
12526            true,
12527            recovered_macro_decorated_declarator_type(node)
12528                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
12529            scope_cache,
12530        ) {
12531            LexicalScopeResolution::Resolved(scope) => scope,
12532            LexicalScopeResolution::Ambiguous => {
12533                if report_stats {
12534                    eprintln!(
12535                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
12536                        lexical_scope_started.elapsed().as_millis(),
12537                    );
12538                }
12539                return LexicalTypeResolution::Ambiguous;
12540            }
12541            LexicalScopeResolution::Missing => {
12542                if report_stats {
12543                    eprintln!(
12544                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
12545                        lexical_scope_started.elapsed().as_millis(),
12546                    );
12547                }
12548                return LexicalTypeResolution::Missing;
12549            }
12550        }
12551    };
12552    if report_stats {
12553        eprintln!(
12554            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
12555            lexical_scope.len(),
12556            lexical_scope_started.elapsed().as_millis(),
12557        );
12558    }
12559    resolve_type_components_lexically_at_scoped(
12560        node,
12561        components,
12562        global,
12563        analyzer,
12564        visibility,
12565        ordinary_type_imports,
12566        file,
12567        source,
12568        direct_target,
12569        apply_structured_prefilter,
12570        preserve_alias,
12571        allow_compatible_foreign_import,
12572        lexical_scope,
12573    )
12574}
12575
12576#[allow(clippy::too_many_arguments)]
12577fn resolve_type_components_lexically_at_scoped(
12578    node: Node<'_>,
12579    components: &[String],
12580    global: bool,
12581    analyzer: &CppGraphSource<'_>,
12582    visibility: &VisibilityIndex<'_>,
12583    ordinary_type_imports: &OrdinaryTypeImportCell,
12584    file: &ProjectFile,
12585    source: &str,
12586    direct_target: Option<&CodeUnit>,
12587    apply_structured_prefilter: bool,
12588    preserve_alias: bool,
12589    allow_compatible_foreign_import: bool,
12590    mut lexical_scope: Vec<String>,
12591) -> LexicalTypeResolution {
12592    if !global
12593        && components.len() == 1
12594        // The two constant-time conditions run before the ancestor climb: each
12595        // `Node::parent` step re-descends from the root (#1927).
12596        && let Some(target) = direct_target
12597        && lexical_scope
12598            .last()
12599            .is_none_or(|last| last != &components[0])
12600        // A recovered class may contain a real member function nested inside
12601        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
12602        // XMLConstHandle). The nearest function_definition is then the member
12603        // itself, so inspect the complete ancestor chain.
12604        && has_malformed_wrapper_function_definition_ancestor(node)
12605        && let Some(indexed_namespace) =
12606            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
12607        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
12608    {
12609        lexical_scope = indexed_namespace;
12610    }
12611    resolve_type_components_in_authoritative_scope(
12612        node,
12613        components,
12614        global,
12615        analyzer,
12616        visibility,
12617        ordinary_type_imports,
12618        file,
12619        source,
12620        direct_target,
12621        apply_structured_prefilter,
12622        preserve_alias,
12623        allow_compatible_foreign_import,
12624        lexical_scope,
12625    )
12626}
12627
12628/// Resolve within a scope already proven by recovered syntax.
12629///
12630/// Unlike parser-derived scope, this scope must not be replaced with the
12631/// queried target's namespace: doing so would let target guidance override a
12632/// nearer declaration represented by the recovered syntax.
12633///
12634/// A qualifier that names a namespace alias in effect at `node` is rebound
12635/// here, once, for forward and inverse lookup alike: the rebound name is
12636/// looked up from the scope of the alias that supplies it. An ordinary name
12637/// the written spelling reaches at a deeper lexical tier shadows the alias,
12638/// and an alias whose target cannot be established resolves to nothing.
12639#[allow(clippy::too_many_arguments)]
12640pub(super) fn resolve_type_components_in_authoritative_scope(
12641    node: Node<'_>,
12642    components: &[String],
12643    global: bool,
12644    analyzer: &CppGraphSource<'_>,
12645    visibility: &VisibilityIndex<'_>,
12646    ordinary_type_imports: &OrdinaryTypeImportCell,
12647    file: &ProjectFile,
12648    source: &str,
12649    direct_target: Option<&CodeUnit>,
12650    apply_structured_prefilter: bool,
12651    preserve_alias: bool,
12652    allow_compatible_foreign_import: bool,
12653    lexical_scope: Vec<String>,
12654) -> LexicalTypeResolution {
12655    let resolve = |components: &[String], global: bool, lexical_scope: Vec<String>| {
12656        resolve_unaliased_type_components_in_authoritative_scope(
12657            node,
12658            components,
12659            global,
12660            analyzer,
12661            visibility,
12662            ordinary_type_imports,
12663            file,
12664            source,
12665            direct_target,
12666            apply_structured_prefilter,
12667            preserve_alias,
12668            allow_compatible_foreign_import,
12669            lexical_scope,
12670        )
12671    };
12672    let binding =
12673        visibility.bind_namespace_qualifier(file, node, components, global, &lexical_scope);
12674    let Some(depth) = binding.depth() else {
12675        return resolve(components, global, lexical_scope);
12676    };
12677    if let LexicalTypeResolution::Resolved {
12678        components: resolved,
12679        ..
12680    } = visibility.resolve_type_components_lexically(
12681        analyzer,
12682        file,
12683        components,
12684        global,
12685        &lexical_scope,
12686    ) && resolved.len().saturating_sub(components.len()) > depth
12687    {
12688        return resolve(components, global, lexical_scope);
12689    }
12690    match binding {
12691        NamespaceQualifierBinding::Bound {
12692            components,
12693            global,
12694            lexical_scope,
12695            ..
12696        } => resolve(&components, global, lexical_scope),
12697        NamespaceQualifierBinding::Unresolved { .. } => LexicalTypeResolution::Ambiguous,
12698        NamespaceQualifierBinding::Unaliased => unreachable!("an unaliased binding has no depth"),
12699    }
12700}
12701
12702#[allow(clippy::too_many_arguments)]
12703fn resolve_unaliased_type_components_in_authoritative_scope(
12704    node: Node<'_>,
12705    components: &[String],
12706    global: bool,
12707    analyzer: &CppGraphSource<'_>,
12708    visibility: &VisibilityIndex<'_>,
12709    ordinary_type_imports: &OrdinaryTypeImportCell,
12710    file: &ProjectFile,
12711    source: &str,
12712    direct_target: Option<&CodeUnit>,
12713    apply_structured_prefilter: bool,
12714    preserve_alias: bool,
12715    allow_compatible_foreign_import: bool,
12716    lexical_scope: Vec<String>,
12717) -> LexicalTypeResolution {
12718    if apply_structured_prefilter
12719        && direct_target.is_some()
12720        && !preserve_alias
12721        && !global
12722        && components.len() == 1
12723        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
12724    {
12725        return LexicalTypeResolution::Missing;
12726    }
12727    // A recovered macro-prefixed return type can share its global spelling
12728    // with aliases from mutually exclusive included headers. C++ lookup uses
12729    // the declaration physically present earlier in this file; the visibility
12730    // index deliberately retains every configuration alternative. Restore
12731    // that precedence only for the exact macro-decorated head (recovered by
12732    // the older grammar, or its clean macro-declaration reading) and lexical
12733    // tier.
12734    if !global
12735        && components.len() == 1
12736        && (recovered_macro_decorated_type_node(node).is_some()
12737            || macro_modifier_decorated_type_node(node))
12738        && let Some(resolution) = recovered_same_file_type_alias_resolution(
12739            node,
12740            components,
12741            analyzer,
12742            visibility,
12743            file,
12744            direct_target,
12745            &lexical_scope,
12746        )
12747    {
12748        return resolution;
12749    }
12750    if apply_structured_prefilter
12751        && let Some(target) = direct_target
12752        && !preserve_alias
12753        && !visibility.structured_type_reference_may_resolve_to_target(
12754            analyzer,
12755            file,
12756            components,
12757            global,
12758            &lexical_scope,
12759            target,
12760        )
12761    {
12762        return LexicalTypeResolution::Missing;
12763    }
12764    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12765    let lexical_normal_started = Instant::now();
12766    if report_stats {
12767        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
12768    }
12769    let normal = if preserve_alias {
12770        visibility.resolve_type_components_lexically_for_forward(
12771            analyzer,
12772            file,
12773            components,
12774            global,
12775            &lexical_scope,
12776        )
12777    } else {
12778        direct_target.map_or_else(
12779            || {
12780                visibility.resolve_type_components_lexically(
12781                    analyzer,
12782                    file,
12783                    components,
12784                    global,
12785                    &lexical_scope,
12786                )
12787            },
12788            |target| {
12789                visibility.resolve_type_components_lexically_for_target(
12790                    analyzer,
12791                    file,
12792                    components,
12793                    global,
12794                    &lexical_scope,
12795                    target,
12796                )
12797            },
12798        )
12799    };
12800    if report_stats {
12801        eprintln!(
12802            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
12803            lexical_normal_started.elapsed().as_millis(),
12804        );
12805    }
12806    let normal = match normal {
12807        LexicalTypeResolution::Resolved { ref unit, .. }
12808            if !visibility
12809                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
12810                && !direct_target.is_some_and(|target| {
12811                    let candidate_refs = visibility
12812                        .visible_identifier_candidates(file, target.identifier())
12813                        .collect::<Vec<_>>();
12814                    visibility.c_tag_declaration_family_matches_target(
12815                        analyzer,
12816                        file,
12817                        &candidate_refs,
12818                        target,
12819                    )
12820                }) =>
12821        {
12822            LexicalTypeResolution::Missing
12823        }
12824        resolution => resolution,
12825    };
12826    let normal_depth = match &normal {
12827        LexicalTypeResolution::Resolved { components, .. } => {
12828            Some(components.len().saturating_sub(1))
12829        }
12830        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
12831    };
12832    // Ordinary using-declarations participate in unqualified lookup at their
12833    // lexical scope. They therefore replace the resolver's terminal/global
12834    // fallback at the same or a shallower depth. A declaration in a more deeply
12835    // nested named scope is the closer lexical result and remains authoritative.
12836    // Ambiguous imports fail closed unless such a closer declaration exists.
12837    let ordinary_import_started = Instant::now();
12838    if report_stats {
12839        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
12840    }
12841    let ordinary_import_resolution = ordinary_type_import_resolution(
12842        node,
12843        components,
12844        global,
12845        analyzer,
12846        visibility,
12847        ordinary_type_imports,
12848        file,
12849        source,
12850        &lexical_scope,
12851        direct_target,
12852    );
12853    if report_stats {
12854        eprintln!(
12855            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
12856            ordinary_import_started.elapsed().as_millis(),
12857        );
12858    }
12859    let resolution = match ordinary_import_resolution {
12860        OrdinaryTypeImportResolution::Missing => normal,
12861        OrdinaryTypeImportResolution::Resolved {
12862            lexical_depth,
12863            is_direct,
12864            ..
12865        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
12866            || normal_depth.is_some_and(|depth| {
12867                depth > lexical_depth || (!is_direct && depth == lexical_depth)
12868            }) =>
12869        {
12870            normal
12871        }
12872        OrdinaryTypeImportResolution::Resolved {
12873            target,
12874            target_components,
12875            ..
12876        } => visibility.resolve_imported_type_candidate(
12877            analyzer,
12878            file,
12879            &target,
12880            &target_components,
12881            direct_target,
12882            preserve_alias,
12883        ),
12884        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
12885            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
12886        {
12887            normal
12888        }
12889        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12890    };
12891    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12892        return resolution;
12893    }
12894
12895    // A foreign header can contribute an exact using binding from a build
12896    // configuration that is compatible with, but not implied by, the current
12897    // reference. Forward navigation has no "unproven" result channel, so it
12898    // may still navigate through one unique structured import after ordinary
12899    // lookup misses. Inverse attribution keeps using its target-guided path
12900    // and records the same evidence as unproven (#940).
12901    match compatible_foreign_type_import_resolution(
12902        node,
12903        components,
12904        global,
12905        analyzer,
12906        visibility,
12907        ordinary_type_imports,
12908        file,
12909        source,
12910        &lexical_scope,
12911        None,
12912    ) {
12913        OrdinaryTypeImportResolution::Missing => resolution,
12914        OrdinaryTypeImportResolution::Resolved {
12915            target,
12916            target_components,
12917            ..
12918        } => visibility.resolve_imported_type_candidate(
12919            analyzer,
12920            file,
12921            &target,
12922            &target_components,
12923            None,
12924            true,
12925        ),
12926        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12927    }
12928}
12929
12930fn recovered_same_file_type_alias_resolution(
12931    node: Node<'_>,
12932    components: &[String],
12933    analyzer: &CppGraphSource<'_>,
12934    visibility: &VisibilityIndex<'_>,
12935    file: &ProjectFile,
12936    direct_target: Option<&CodeUnit>,
12937    lexical_scope: &[String],
12938) -> Option<LexicalTypeResolution> {
12939    debug_assert_eq!(components.len(), 1);
12940    debug_assert!(
12941        recovered_macro_decorated_type_node(node).is_some()
12942            || macro_modifier_decorated_type_node(node)
12943    );
12944    let alias_provider = analyzer.type_alias_provider()?;
12945    for qualified in lexical_component_tiers(components, false, lexical_scope) {
12946        let candidates = visibility
12947            .visible_identifier_candidates(file, &components[0])
12948            .filter(|candidate| {
12949                candidate.source() == file
12950                    && canonical_cpp_scope_components(candidate) == qualified
12951                    && visibility
12952                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12953            })
12954            .collect::<Vec<_>>();
12955        if candidates.is_empty() {
12956            continue;
12957        }
12958        if candidates
12959            .iter()
12960            .any(|candidate| !alias_provider.is_type_alias(candidate))
12961        {
12962            return None;
12963        }
12964        let unit = if let Some(target) = direct_target {
12965            visibility.unique_type_candidate_preserving_target(
12966                analyzer,
12967                file,
12968                &candidates,
12969                target,
12970            )?
12971        } else {
12972            let first = candidates[0];
12973            if candidates
12974                .iter()
12975                .any(|candidate| !same_visible_symbol(candidate, first))
12976            {
12977                return None;
12978            }
12979            first.clone()
12980        };
12981        return Some(LexicalTypeResolution::Resolved {
12982            unit,
12983            components: qualified,
12984            candidates: candidates.into_iter().cloned().collect(),
12985        });
12986    }
12987    None
12988}
12989
12990fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12991    matches!(
12992        structured_owner_context_resolution(node, ctx),
12993        StructuredOwnerContextResolution::SelfTarget
12994            | StructuredOwnerContextResolution::InheritedTarget
12995    )
12996}
12997
12998/// A bare/`this->` member call whose name resolves, through the enclosing class's base
12999/// hierarchy or through a lexically enclosing class, to the target member declared on
13000/// another owner. This is a genuine external usage of that owner's member rather than a
13001/// same-type self call.
13002fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13003    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
13004        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
13005    }) else {
13006        return matches!(
13007            structured_owner_context_resolution(node, ctx),
13008            StructuredOwnerContextResolution::InheritedTarget
13009        );
13010    };
13011    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13012        return false;
13013    };
13014    let chain = structured_enclosing_owner_chain(node, ctx);
13015    let Some(innermost) = chain.first() else {
13016        return false;
13017    };
13018    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13019        return false;
13020    }
13021    let Some(arity) = ctx
13022        .visibility
13023        .call_arity_evidence(ctx.file, call, ctx.source)
13024        .exact()
13025    else {
13026        return false;
13027    };
13028    for enclosing_owner in &chain {
13029        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13030            return true;
13031        }
13032        match resolve_declaring_callable_owner(
13033            &ctx.analyzer,
13034            ctx.visibility,
13035            ctx.file,
13036            cached_declaring_member_owner(enclosing_owner, ctx),
13037            &ctx.spec.member_name,
13038            arity,
13039        ) {
13040            EnclosingMemberOwnerResolution::Owner(owner) => {
13041                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13042            }
13043            EnclosingMemberOwnerResolution::Ambiguous => return false,
13044            // The name is not declared in this class or its bases, so C++
13045            // unqualified lookup continues in the next enclosing class (#3095).
13046            EnclosingMemberOwnerResolution::Missing => {}
13047        }
13048    }
13049    false
13050}
13051
13052fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13053    matches!(
13054        structured_owner_context_resolution(node, ctx),
13055        StructuredOwnerContextResolution::NonTarget
13056    )
13057}
13058
13059fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13060    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13061        return false;
13062    };
13063    let mut current = ctx.ancestry.parent(node);
13064    while let Some(parent) = current {
13065        if parent.kind() == "function_definition" {
13066            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
13067                return false;
13068            };
13069            if let Some(owners) = out_of_line_member_definition_owner(
13070                &ctx.analyzer,
13071                ctx.visibility,
13072                ctx.file,
13073                ctx.source,
13074                owner_lookup,
13075            ) && let Some((_, owner)) = owners.innermost()
13076            {
13077                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
13078            }
13079            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
13080                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13081            }
13082            return false;
13083        }
13084        current = ctx.ancestry.parent(parent);
13085    }
13086    false
13087}
13088
13089#[derive(Clone, Copy)]
13090enum StructuredOwnerContextResolution {
13091    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
13092    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
13093    SelfTarget,
13094    /// The enclosing class does not declare the member; lookup reaches the target owner
13095    /// through a base of that class, or through a lexically enclosing class (a nested
13096    /// class calling the outer class's member). Either way the reference is a genuine
13097    /// external usage OF that owner's member (e.g. `Derived` calling inherited
13098    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
13099    InheritedTarget,
13100    NonTarget,
13101    Ambiguous,
13102    Missing,
13103}
13104
13105fn structured_owner_context_resolution(
13106    node: Node<'_>,
13107    ctx: &ScanCtx<'_>,
13108) -> StructuredOwnerContextResolution {
13109    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13110        return StructuredOwnerContextResolution::Missing;
13111    };
13112    let chain = structured_enclosing_owner_chain(node, ctx);
13113    let Some(innermost) = chain.first() else {
13114        return StructuredOwnerContextResolution::Missing;
13115    };
13116    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13117        return StructuredOwnerContextResolution::SelfTarget;
13118    }
13119    // Unqualified lookup searches the enclosing class and its bases, then each lexically
13120    // enclosing class outward, and stops at the first scope that declares the name (#3095).
13121    // Only the immediately enclosing class makes a reference a same-type self call; any
13122    // other owner it reaches is a genuine usage of that owner's member.
13123    for enclosing_owner in &chain {
13124        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13125            return StructuredOwnerContextResolution::InheritedTarget;
13126        }
13127        match cached_declaring_member_owner(enclosing_owner, ctx) {
13128            EnclosingMemberOwnerResolution::Owner(owner)
13129                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
13130            {
13131                return StructuredOwnerContextResolution::InheritedTarget;
13132            }
13133            EnclosingMemberOwnerResolution::Owner(_) => {
13134                return StructuredOwnerContextResolution::NonTarget;
13135            }
13136            EnclosingMemberOwnerResolution::Ambiguous => {
13137                return StructuredOwnerContextResolution::Ambiguous;
13138            }
13139            EnclosingMemberOwnerResolution::Missing => {}
13140        }
13141    }
13142    StructuredOwnerContextResolution::Missing
13143}
13144
13145fn cached_declaring_member_owner(
13146    receiver_owner: &CodeUnit,
13147    ctx: &ScanCtx<'_>,
13148) -> EnclosingMemberOwnerResolution {
13149    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
13150        return cached;
13151    }
13152    let resolved = resolve_declaring_member_owner(
13153        &ctx.analyzer,
13154        ctx.visibility,
13155        ctx.file,
13156        receiver_owner,
13157        &ctx.spec.member_name,
13158    );
13159    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13160        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13161    } else {
13162        resolved
13163    };
13164    ctx.member_owner_cache
13165        .borrow_mut()
13166        .insert(receiver_owner.clone(), resolved.clone());
13167    resolved
13168}
13169
13170/// Recover a member's declaring owner when parser recovery omitted its
13171/// in-class declaration but retained an out-of-line definition. Ordinary
13172/// visible-member lookup runs first. The structured definition index then
13173/// supplies the missing member fact at each hierarchy level, so an indexed
13174/// derived override still hides the queried base member and distinct base
13175/// paths still fail closed.
13176fn indexed_declaring_owner_for_recovered_member(
13177    receiver_owner: &CodeUnit,
13178    ctx: &ScanCtx<'_>,
13179) -> EnclosingMemberOwnerResolution {
13180    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13181        return EnclosingMemberOwnerResolution::Missing;
13182    };
13183    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13184        return EnclosingMemberOwnerResolution::Missing;
13185    }
13186    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13187        return EnclosingMemberOwnerResolution::Missing;
13188    };
13189    let Some(receiver_owner) =
13190        ctx.visibility
13191            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13192    else {
13193        return EnclosingMemberOwnerResolution::Ambiguous;
13194    };
13195    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13196        ctx.visibility
13197            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13198    }) else {
13199        return EnclosingMemberOwnerResolution::Missing;
13200    };
13201
13202    let owner_declares_member = |owner: &CodeUnit| {
13203        if same_visible_symbol(owner, &target_owner) {
13204            return true;
13205        }
13206        let mut member_fq = owner.fq().clone();
13207        member_fq.push(
13208            ctx.spec
13209                .target
13210                .fq()
13211                .last()
13212                .expect("a method target has a terminal member segment"),
13213        );
13214        ctx.analyzer
13215            .definitions(&member_fq.display(segment_interner()))
13216            .any(|child| child.is_function())
13217    };
13218    if owner_declares_member(&receiver_owner) {
13219        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13220    }
13221
13222    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
13223    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13224    let mut declaring_owner = None;
13225    while let Some(raw_owner) = stack.pop() {
13226        let Some(owner) =
13227            ctx.visibility
13228                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13229        else {
13230            return EnclosingMemberOwnerResolution::Ambiguous;
13231        };
13232        let propagated = propagated_counts.entry(owner.clone()).or_default();
13233        if *propagated == 2 {
13234            continue;
13235        }
13236        *propagated += 1;
13237        if owner_declares_member(&owner) {
13238            if declaring_owner.is_some() {
13239                return EnclosingMemberOwnerResolution::Ambiguous;
13240            }
13241            declaring_owner = Some(owner);
13242            continue;
13243        }
13244        stack.extend(hierarchy.get_direct_ancestors(&owner));
13245    }
13246    declaring_owner
13247        .map(EnclosingMemberOwnerResolution::Owner)
13248        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13249}
13250
13251/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13252/// lookup searches each in turn before it reaches the enclosing namespaces.
13253fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13254    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13255        return Vec::new();
13256    };
13257    // The innermost owner is whatever enclosing-owner resolution produced, so
13258    // it is kept as given; only the walk outward is filtered to classes.
13259    brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13260        ctx.analyzer.parent_of(unit)
13261    })
13262    .collect()
13263}
13264
13265fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13266    // Declaration recovery can index the true class/member ranges even when
13267    // the original error tree wraps that region in a bogus function. Prefer
13268    // the analyzer's exact enclosing-owner graph at the reference byte before
13269    // interpreting such a wrapper as a real callable owner.
13270    if (has_recovered_class_shape_ancestor(node)
13271        || has_malformed_wrapper_function_definition_ancestor(node))
13272        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13273    {
13274        return Some(owner);
13275    }
13276    let mut current = ctx.ancestry.parent(node);
13277    while let Some(parent) = current {
13278        if parent.kind() == "function_definition" {
13279            let owner_lookup = function_definition_owner_lookup_node(parent);
13280            if let Some(owner_lookup) = owner_lookup
13281                && let Some(owners) = out_of_line_member_definition_owner(
13282                    &ctx.analyzer,
13283                    ctx.visibility,
13284                    ctx.file,
13285                    ctx.source,
13286                    owner_lookup,
13287                )
13288                && let Some((_, owner)) = owners.innermost()
13289            {
13290                return Some(owner.clone());
13291            }
13292            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13293                return Some(owner);
13294            }
13295            if let Some(owner) = enclosing_context(parent, ctx)
13296                .owner
13297                .filter(|owner| owner.is_class())
13298            {
13299                return Some(owner);
13300            }
13301            if let Some(owner_lookup) = owner_lookup
13302                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13303            {
13304                return Some(owner);
13305            }
13306            break;
13307        }
13308        current = ctx.ancestry.parent(parent);
13309    }
13310    enclosing_context(node, ctx)
13311        .owner
13312        .filter(|owner| owner.is_class())
13313}
13314
13315fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13316    let target_owner = ctx.spec.owner.as_ref()?;
13317    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13318    let owner_name = owner_components.last()?;
13319    let mut candidates = Vec::new();
13320    for candidate in ctx
13321        .visibility
13322        .visible_identifier_candidates(ctx.file, owner_name)
13323        .filter(|candidate| candidate.is_class())
13324    {
13325        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13326            brokk_bifrost_core::analyzer::Language::Cpp,
13327            &cpp_name_for(candidate),
13328        );
13329        if !components.ends_with(&owner_components)
13330            || candidates
13331                .iter()
13332                .any(|existing| same_logical_symbol(existing, candidate))
13333        {
13334            continue;
13335        }
13336        candidates.push(candidate.clone());
13337    }
13338    let [candidate] = candidates.as_slice() else {
13339        return None;
13340    };
13341    (same_logical_symbol(candidate, target_owner)
13342        && target_group_contains_owner_peer(candidate, ctx))
13343    .then(|| candidate.clone())
13344}