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::memo_attribution::CaptureScopedMemo;
30use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
31use brokk_bifrost_core::analyzer::query_token::QueryToken;
32use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_is_within;
33use brokk_bifrost_core::analyzer::tree_walk::{
34    ParentIndex, WalkControl, children_iter, first_named_child_of_kind, parent_if_field,
35    push_named_children_reversed, root_node, walk_named_tree_preorder,
36};
37use brokk_bifrost_core::analyzer::usages::accumulator::UsageAccumulator;
38use brokk_bifrost_core::analyzer::usages::common::same_node;
39use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
40use brokk_bifrost_core::analyzer::usages::local_inference::{
41    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
42};
43use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
44use brokk_bifrost_core::hash::{HashMap, HashSet};
45#[cfg(any(test, feature = "test-support"))]
46use std::cell::Cell;
47use std::cell::RefCell;
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 ScanCtx<'a> {
69    pub analyzer: CppGraphSource<'a>,
70    pub visibility: &'a VisibilityIndex<'a>,
71    pub file: &'a ProjectFile,
72    pub source: &'a str,
73    /// The parent of every node in the file being scanned, recorded by one
74    /// downward pass when the scan starts. `Node::parent` recovers a parent by
75    /// re-descending from the root, so an ancestor climb is quadratic in depth
76    /// and the scan climbs from more than a dozen call sites per visited node
77    /// (#1927, #3097).
78    pub ancestry: ParentIndex<'a>,
79    ordinary_type_imports: OrdinaryTypeImportCell,
80    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
81    class_ranges: Option<&'a ClassRangeIndex>,
82    pub line_starts: &'a [usize],
83    pub spec: &'a TargetSpec,
84    pub target_group: &'a HashSet<CodeUnit>,
85    pub has_proven_visible_type_target: bool,
86    uses_c_semantics: bool,
87    type_reference_component_names: HashSet<String>,
88    pub target_declaration_ranges: Vec<Range>,
89    target_macro_declaration_bytes: Vec<usize>,
90    pub bindings: LocalInferenceEngine<CppScanBinding>,
91    local_shadows: LocalInferenceEngine<()>,
92    using_enum_owners: ScopedUsingEnumOwners,
93    semantic_using_enum_owners: SemanticUsingEnumOwners,
94    needs_using_enum_member_resolution: bool,
95    pub usages: &'a mut UsageAccumulator,
96    pub enclosing_cache: CaptureScopedMemo<RefCell<HashMap<(usize, usize), EnclosingContext>>>,
97    pub enclosing_owner_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, Option<CodeUnit>>>>,
98    lexical_scope_cache: LexicalScopeCache,
99    lexical_free_function_cache: CaptureScopedMemo<RefCell<HashMap<(String, String), bool>>>,
100    member_owner_cache:
101        CaptureScopedMemo<RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>>,
102    global_field_internal_linkage_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, bool>>>,
103    receiver_canonical_type_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, Option<CodeUnit>>>>,
104}
105
106impl ScanCtx<'_> {
107    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
108        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
109    }
110}
111
112#[derive(Clone, Default)]
113pub struct EnclosingContext {
114    pub enclosing: Option<CodeUnit>,
115    pub owner: Option<CodeUnit>,
116}
117
118pub fn prepare_file(
119    cpp: &dyn CppSource,
120    token: QueryToken<'_>,
121    file: &ProjectFile,
122) -> Option<Arc<PreparedSyntaxTree>> {
123    cpp.prepared_syntax(token, file)
124}
125
126#[allow(clippy::too_many_arguments)]
127pub fn scan_prepared_file(
128    analyzer: &CppGraphSource<'_>,
129    visibility: &VisibilityIndex<'_>,
130    file: &ProjectFile,
131    prepared: &PreparedSyntaxTree,
132    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
133    class_ranges: Option<&ClassRangeIndex>,
134    spec: &TargetSpec,
135    target_group: &HashSet<CodeUnit>,
136    usages: &mut UsageAccumulator,
137) {
138    if usages.limit_reached() {
139        return;
140    }
141    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
142    let has_proven_visible_type_target = spec.kind == TargetKind::Type
143        && (target_group.iter().any(|target| {
144            same_logical_symbol(target, &spec.target)
145                && visibility.is_physically_visible(file, target)
146        }) || {
147            let candidates = visibility
148                .visible_identifier_candidates(file, spec.target.identifier())
149                .collect::<Vec<_>>();
150            visibility.c_tag_declaration_family_matches_target(
151                analyzer,
152                file,
153                &candidates,
154                &spec.target,
155            )
156        });
157    if spec.kind == TargetKind::Type
158        && !has_proven_visible_type_target
159        && visibility
160            .visible_identifier_candidates(file, spec.target.identifier())
161            .any(|candidate| {
162                candidate != &spec.target
163                    && !target_group.contains(candidate)
164                    && same_logical_symbol(candidate, &spec.target)
165                    && visibility.is_physically_visible(file, candidate)
166                    && !visibility.c_tag_declaration_family_matches_target(
167                        analyzer,
168                        file,
169                        std::slice::from_ref(&candidate),
170                        &spec.target,
171                    )
172            })
173    {
174        return;
175    }
176    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
177        analyzer.ranges(&spec.target)
178    } else {
179        Vec::new()
180    };
181    let target_declaration_ranges = if spec.kind == TargetKind::Type {
182        target_group
183            .iter()
184            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
185            .flat_map(|target| analyzer.ranges(target))
186            .collect()
187    } else if spec.target.source() == file {
188        if spec.kind == TargetKind::Macro {
189            macro_target_declaration_ranges.clone()
190        } else {
191            analyzer.ranges(&spec.target)
192        }
193    } else {
194        Vec::new()
195    };
196    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
197        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
198    } else {
199        Vec::new()
200    };
201    let type_reference_component_names = if spec.kind == TargetKind::Type {
202        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
203    } else {
204        HashSet::default()
205    };
206    if spec.kind == TargetKind::Type
207        && !file_may_reference_type_target(
208            prepared.tree().root_node(),
209            prepared.source(),
210            analyzer,
211            visibility,
212            file,
213            &spec.target,
214            &type_reference_component_names,
215        )
216    {
217        return;
218    }
219    let ordinary_type_imports = initialized_ordinary_type_imports(
220        prepared.tree().root_node(),
221        analyzer,
222        visibility,
223        file,
224        prepared.source(),
225    );
226    let mut ctx = ScanCtx {
227        analyzer: *analyzer,
228        visibility,
229        file,
230        source: prepared.source(),
231        ancestry: ParentIndex::new(prepared.tree().root_node()),
232        ordinary_type_imports,
233        recovered_sentinel_classes,
234        class_ranges,
235        line_starts: prepared.line_starts(),
236        spec,
237        target_group,
238        has_proven_visible_type_target,
239        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
240        type_reference_component_names,
241        target_declaration_ranges,
242        target_macro_declaration_bytes,
243        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
244        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
245        using_enum_owners: ScopedUsingEnumOwners::new(),
246        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
247        needs_using_enum_member_resolution,
248        usages,
249        enclosing_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
250        enclosing_owner_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
251        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
252        lexical_free_function_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
253        member_owner_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
254        global_field_internal_linkage_cache: CaptureScopedMemo::new(RefCell::new(
255            HashMap::default(),
256        )),
257        receiver_canonical_type_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
258    };
259    if needs_using_enum_member_resolution {
260        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
261    }
262    if spec.kind == TargetKind::Macro {
263        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
264        return;
265    }
266    scan_node(prepared.tree().root_node(), &mut ctx);
267}
268
269/// Scan a macro target without maintaining the declaration and lexical state
270/// required by ordinary C and C++ symbols. Macro activation comes entirely
271/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
272/// node makes generated tables needlessly expensive.
273fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
274    walk_named_tree_preorder(root, true, |node| {
275        if ctx.usages.limit_reached() {
276            return WalkControl::Break;
277        }
278        maybe_record_macro_hit(node, ctx);
279        WalkControl::Continue
280    });
281}
282
283enum UsingEnumDeclarationScope {
284    Block,
285    Class(CodeUnit),
286    Namespace(Vec<String>),
287    UnsupportedClass,
288}
289
290fn resolved_using_enum_declaration(
291    node: Node<'_>,
292    ctx: &ScanCtx<'_>,
293) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
294    let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
295        node,
296        &ctx.analyzer,
297        ctx.visibility,
298        &ctx.ordinary_type_imports,
299        ctx.file,
300        ctx.source,
301    ) else {
302        return None;
303    };
304    Some((unit, using_enum_declaration_scope(node, ctx)))
305}
306
307fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
308    let mut current = ctx.ancestry.parent(node);
309    while let Some(parent) = current {
310        if matches!(
311            parent.kind(),
312            "compound_statement"
313                | "function_definition"
314                | "lambda_expression"
315                | "for_statement"
316                | "while_statement"
317                | "if_statement"
318        ) {
319            return UsingEnumDeclarationScope::Block;
320        }
321        if matches!(
322            parent.kind(),
323            "class_specifier" | "struct_specifier" | "union_specifier"
324        ) {
325            let resolution = enclosing_lexical_scope_components(
326                node,
327                &ctx.analyzer,
328                ctx.visibility,
329                ctx.file,
330                ctx.source,
331            );
332            if let LexicalScopeResolution::Resolved(components) = resolution
333                && let LexicalTypeResolution::Resolved { unit, .. } =
334                    ctx.visibility.resolve_type_components_lexically(
335                        &ctx.analyzer,
336                        ctx.file,
337                        &components,
338                        true,
339                        &[],
340                    )
341            {
342                return UsingEnumDeclarationScope::Class(unit);
343            }
344            return UsingEnumDeclarationScope::UnsupportedClass;
345        }
346        current = ctx.ancestry.parent(parent);
347    }
348    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
349}
350
351fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
352    let mut stack = vec![root];
353    while let Some(node) = stack.pop() {
354        if node.kind() == "using_declaration"
355            && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
356        {
357            match scope {
358                UsingEnumDeclarationScope::Block => {}
359                UsingEnumDeclarationScope::Class(class) => {
360                    ctx.semantic_using_enum_owners.import_class(class, unit);
361                }
362                UsingEnumDeclarationScope::Namespace(namespace) => {
363                    ctx.semantic_using_enum_owners.import_namespace(
364                        namespace,
365                        node.start_byte(),
366                        unit,
367                    );
368                }
369                UsingEnumDeclarationScope::UnsupportedClass => {}
370            }
371        }
372        push_named_children_reversed(node, &mut stack);
373    }
374}
375
376fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
377    struct Frame<'tree> {
378        node: Node<'tree>,
379        next_child: Option<Node<'tree>>,
380        previous_child: Option<Node<'tree>>,
381        entered: bool,
382        enters_scope: bool,
383        enters_using_enum_scope: bool,
384        fractured_function_scope: bool,
385    }
386    impl<'tree> Frame<'tree> {
387        fn new(node: Node<'tree>) -> Self {
388            Self {
389                node,
390                next_child: node.named_child(0),
391                previous_child: None,
392                entered: false,
393                enters_scope: false,
394                enters_using_enum_scope: false,
395                fractured_function_scope: false,
396            }
397        }
398    }
399    if ctx.usages.limit_reached() {
400        return;
401    }
402    let mut stack = vec![Frame::new(node)];
403    while let Some(current) = stack.last_mut() {
404        let node = current.node;
405        let translation_unit = node.kind() == "translation_unit";
406        if !current.entered {
407            current.entered = true;
408            current.enters_scope = matches!(
409                node.kind(),
410                "compound_statement"
411                    | "function_definition"
412                    | "lambda_expression"
413                    | "for_statement"
414                    | "for_range_loop"
415                    | "while_statement"
416                    | "if_statement"
417            );
418            current.enters_using_enum_scope = ctx.needs_using_enum_member_resolution
419                && (current.enters_scope
420                    || matches!(
421                        node.kind(),
422                        "namespace_definition"
423                            | "class_specifier"
424                            | "struct_specifier"
425                            | "union_specifier"
426                    ));
427            if current.enters_scope {
428                ctx.bindings.enter_scope();
429                ctx.local_shadows.enter_scope();
430            }
431            if current.enters_using_enum_scope {
432                ctx.using_enum_owners.enter_scope();
433            }
434            seed_declarations(node, ctx);
435            maybe_record_hit(node, ctx);
436        }
437        if let Some(child) = current.previous_child.take()
438            && !ctx.usages.limit_reached()
439            && translation_unit
440            && macro_fractured_function(child)
441            && macro_fracture_boundary(child).is_some()
442        {
443            if current.fractured_function_scope {
444                ctx.bindings.exit_scope();
445                ctx.local_shadows.exit_scope();
446            }
447            ctx.bindings.enter_scope();
448            ctx.local_shadows.enter_scope();
449            seed_fractured_function_prefix(child, ctx);
450            current.fractured_function_scope = true;
451        }
452        // Unwind every entered scope once the hit limit has been reached.
453        if !ctx.usages.limit_reached()
454            && let Some(child) = current.next_child.take()
455        {
456            current.next_child = child.next_named_sibling();
457            if translation_unit && current.fractured_function_scope && child.kind() == "ERROR" {
458                ctx.bindings.exit_scope();
459                ctx.local_shadows.exit_scope();
460                current.fractured_function_scope = false;
461            }
462            current.previous_child = Some(child);
463            stack.push(Frame::new(child));
464            continue;
465        }
466        if current.fractured_function_scope {
467            ctx.bindings.exit_scope();
468            ctx.local_shadows.exit_scope();
469        }
470        if current.enters_scope {
471            ctx.bindings.exit_scope();
472            ctx.local_shadows.exit_scope();
473        }
474        if current.enters_using_enum_scope {
475            ctx.using_enum_owners.exit_scope();
476        }
477        stack.pop();
478    }
479}
480
481fn macro_fractured_function(node: Node<'_>) -> bool {
482    if node.kind() != "function_definition" {
483        return false;
484    }
485    let Some(body) = node.child_by_field_name("body") else {
486        return false;
487    };
488    let mut stack = vec![body];
489    while let Some(current) = stack.pop() {
490        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
491            return true;
492        }
493        let mut cursor = current.walk();
494        stack.extend(current.named_children(&mut cursor));
495    }
496    false
497}
498
499/// Return the recovery boundary following a fractured macro-generated callable.
500pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
501    let first_orphan = node.next_named_sibling()?;
502    if !matches!(
503        first_orphan.kind(),
504        "expression_statement"
505            | "compound_statement"
506            | "preproc_if"
507            | "preproc_ifdef"
508            | "if_statement"
509            | "for_statement"
510            | "while_statement"
511            | "do_statement"
512            | "return_statement"
513    ) {
514        return None;
515    }
516    let mut sibling = Some(first_orphan);
517    while let Some(current) = sibling {
518        if current.kind() == "ERROR" {
519            return Some(current.end_byte());
520        }
521        if matches!(
522            current.kind(),
523            "function_definition" | "declaration" | "type_definition"
524        ) {
525            return None;
526        }
527        sibling = current.next_named_sibling();
528    }
529    None
530}
531
532fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
533    let cutoff = node.end_byte().saturating_sub(1);
534    seed_fractured_active_path(node, cutoff, ctx);
535}
536
537fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
538    let mut stack = vec![node];
539    while let Some(node) = stack.pop() {
540        if node.start_byte() >= cutoff {
541            continue;
542        }
543        let enters_scope = matches!(
544            node.kind(),
545            "compound_statement"
546                | "function_definition"
547                | "lambda_expression"
548                | "for_range_loop"
549                | "for_statement"
550                | "while_statement"
551                | "if_statement"
552                | "class_specifier"
553                | "struct_specifier"
554                | "union_specifier"
555        );
556        if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
557            continue;
558        }
559        seed_declarations(node, ctx);
560        push_named_children_reversed(node, &mut stack);
561    }
562}
563
564fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
565    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
566        || (ctx.spec.kind != TargetKind::Type
567            && indexed_recovered_class_field_declaration(node, ctx))
568    {
569        return;
570    }
571    match node.kind() {
572        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
573        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
574        "for_range_loop" => seed_range_binding(node, ctx),
575        "expression_statement" | "macro_type_specifier" => {
576            seed_function_macro_local_binding(node, ctx)
577        }
578        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
579        "using_declaration" => seed_using_enum(node, ctx),
580        _ => {}
581    }
582}
583
584fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
585    let Some(binding) = ctx
586        .visibility
587        .function_macro_local_binding(ctx.file, node, ctx.source)
588    else {
589        return;
590    };
591    if ctx.spec.kind == TargetKind::Type {
592        ctx.bindings.declare_shadow(binding.name);
593        return;
594    }
595    let normalized = normalize_cpp_type_name(&binding.type_name);
596    let unit = binding
597        .type_node
598        .and_then(|type_node| {
599            ctx.visibility
600                .resolve_type_node_result(ctx.file, type_node, ctx.source)
601                .ok()
602                .flatten()
603        })
604        .or_else(|| {
605            ctx.visibility
606                .canonical_type_for_reference(ctx.file, &normalized)
607        })
608        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
609    ctx.bindings.seed_symbol(
610        binding.name,
611        CppScanBinding::from_type_name(
612            normalized,
613            unit,
614            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
615        ),
616    );
617}
618
619fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
620    let Some(binding) =
621        ctx.visibility
622            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
623    else {
624        return;
625    };
626    let normalized = normalize_cpp_type_name(&binding.type_name);
627    let unit = binding.proven_unit.clone().or_else(|| {
628        binding
629            .type_node
630            .and_then(|type_node| {
631                ctx.visibility
632                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
633                    .ok()
634                    .flatten()
635            })
636            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
637    });
638    if let Some(unit) = unit {
639        ctx.bindings.seed_symbol(
640            binding.name,
641            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
642        );
643    }
644}
645
646fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
647    if node.kind() != "declaration"
648        || !has_function_scope_ancestor(node)
649        || has_ancestor_kind(node, "lambda_expression")
650        || !(has_recovered_class_shape_ancestor(node)
651            || has_malformed_wrapper_function_definition_ancestor(node))
652    {
653        return false;
654    }
655    let context = enclosing_context(node, ctx);
656    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
657        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
658}
659
660fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
661    if !ctx.needs_using_enum_member_resolution {
662        return;
663    }
664    if let Some((unit, UsingEnumDeclarationScope::Block)) =
665        resolved_using_enum_declaration(node, ctx)
666    {
667        ctx.using_enum_owners.import(unit);
668    }
669}
670
671fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
672    // Class and local-struct fields are resolved from their enclosing owner
673    // when they appear as an unqualified receiver. Seeding them into the
674    // function-wide binding scope would make same-spelled fields from sibling
675    // owners overwrite one another before the owner-aware path runs.
676    if node.kind() == "field_declaration" {
677        return;
678    }
679    let type_node = node
680        .child_by_field_name("type")
681        .or_else(|| first_type_child(node));
682    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
683    let mut cursor = node.walk();
684    for child in node.named_children(&mut cursor) {
685        let declarator = declaration_declarator(node, child);
686        let Some(declarator) = declarator else {
687            continue;
688        };
689        let Some(name) = extract_variable_name(declarator, ctx.source) else {
690            continue;
691        };
692        if declarator.kind() == "function_declarator"
693            && !constructor_style_local_declaration(
694                ctx.visibility,
695                ctx.file,
696                ctx.source,
697                declarator,
698                type_text.as_deref(),
699                &ctx.bindings,
700            )
701        {
702            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
703                ctx.local_shadows.declare_shadow(name);
704            }
705            continue;
706        }
707        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
708            ctx.local_shadows.declare_shadow(name.clone());
709        }
710        if ctx.spec.kind == TargetKind::Type {
711            ctx.bindings.declare_shadow(name);
712            continue;
713        }
714        let value = child.child_by_field_name("value");
715        seed_binding_from_type_or_value(&name, type_node, value, ctx);
716    }
717}
718
719fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
720    if !parameter_belongs_to_callable_scope(node) {
721        return;
722    }
723    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
724        return;
725    };
726    if has_function_scope_ancestor(node) {
727        ctx.local_shadows.declare_shadow(name.clone());
728    }
729    if ctx.spec.kind == TargetKind::Type {
730        ctx.bindings.declare_shadow(name);
731        return;
732    }
733    seed_binding_from_type_or_value(&name, type_node, None, ctx);
734}
735
736fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
737    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
738        return;
739    };
740    if has_function_scope_ancestor(node) {
741        ctx.local_shadows.declare_shadow(name.clone());
742    }
743    if ctx.spec.kind == TargetKind::Type {
744        ctx.bindings.declare_shadow(name);
745        return;
746    }
747    seed_binding_from_type_or_value(&name, type_node, None, ctx);
748}
749
750fn has_function_scope_ancestor(node: Node<'_>) -> bool {
751    let mut current = node.parent();
752    while let Some(parent) = current {
753        // Error recovery can wrap a complete namespace in a bogus outer
754        // `function_definition` (for example when an object-like namespace
755        // macro is parsed as a return type).  Declarations below that
756        // namespace are still namespace-scoped: do not let the malformed
757        // callable envelope seed them as local shadows.  A real function
758        // body is encountered before its enclosing namespace, so this keeps
759        // ordinary local binding detection unchanged.
760        if parent.kind() == "namespace_definition" {
761            return false;
762        }
763        if parent.kind() == "function_definition" {
764            // The malformed sentinel envelope is not a callable scope. Its
765            // body may contain a recovered namespace (or, in a smaller error
766            // tree, only an ERROR node standing in for that namespace), so
767            // declarations directly below it must remain namespace-scoped.
768            // Real nested functions are encountered first and still seed
769            // ordinary local bindings.
770            return !is_malformed_wrapper_function_definition(parent);
771        }
772        if parent.kind() == "lambda_expression" {
773            return true;
774        }
775        current = parent.parent();
776    }
777    false
778}
779
780fn seed_binding_from_type_or_value(
781    name: &str,
782    type_node: Option<Node<'_>>,
783    value: Option<Node<'_>>,
784    ctx: &mut ScanCtx<'_>,
785) {
786    if name.is_empty() {
787        return;
788    }
789    let resolved = type_node
790        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
791        .map(|node| {
792            let text = node_text(node, ctx.source);
793            let name = normalize_cpp_type_name(text);
794            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
795                return CppScanBinding::from_type_name(
796                    name,
797                    Some(unit),
798                    cpp_type_text_pointer_depth(text),
799                );
800            }
801            if let Some(unit) = function_local_type_for_node(node, ctx) {
802                return CppScanBinding::from_type_name(
803                    name,
804                    unit,
805                    cpp_type_text_pointer_depth(text),
806                );
807            }
808            // Bare type names need lexical ownership before the coarse visible-name
809            // fallback (two namespaces can each declare `CopyResult`). Template
810            // references keep the specialization-aware resolver first because a
811            // component-only lexical lookup cannot rank partial specializations.
812            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
813                if cpp_template_reference_arguments(node, ctx.source).is_some() {
814                    return None;
815                }
816                match enclosing_lexical_scope_components(
817                    node,
818                    &ctx.analyzer,
819                    ctx.visibility,
820                    ctx.file,
821                    ctx.source,
822                ) {
823                    LexicalScopeResolution::Resolved(scope) => Some(scope),
824                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
825                }
826            });
827            let unit = lexical_scope
828                .as_deref()
829                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
830                .or_else(|| {
831                    match ctx
832                        .visibility
833                        .resolve_type_node_result(ctx.file, node, ctx.source)
834                    {
835                        Ok(Some(unit)) => Some(unit),
836                        Ok(None) => ctx
837                            .visibility
838                            .canonical_type_for_reference(ctx.file, &name)
839                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
840                        Err(_) => None,
841                    }
842                });
843            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
844        })
845        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
846
847    if let Some(resolved) = resolved {
848        ctx.bindings.seed_symbol(name.to_string(), resolved);
849    } else if let Some(value) = value
850        && value.kind() == "identifier"
851    {
852        ctx.bindings
853            .alias_symbol(name.to_string(), node_text(value, ctx.source));
854    } else {
855        ctx.bindings.declare_shadow(name.to_string());
856    }
857}
858
859fn resolve_seed_type_node_lexically(
860    node: Node<'_>,
861    ctx: &ScanCtx<'_>,
862    scope: &[String],
863) -> Option<CodeUnit> {
864    let (components, global) = type_reference_components(node, ctx.source)?;
865    let resolution = match scan_owner_type(ctx) {
866        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
867            &ctx.analyzer,
868            ctx.file,
869            &components,
870            global,
871            scope,
872            target,
873        ),
874        None => ctx.visibility.resolve_type_components_lexically(
875            &ctx.analyzer,
876            ctx.file,
877            &components,
878            global,
879            scope,
880        ),
881    };
882    match resolution {
883        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
884        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
885    }
886}
887
888/// The type this scan is asking about: the owner of a member target, or the
889/// target itself when it is a type.
890///
891/// C headers routinely declare one typedef name once per branch of an
892/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
893/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
894/// are alternate spellings of one name rather than competing declarations, and
895/// canonicalizing without the scan's own type identity picks between them by
896/// declaration order. That ordering can bind a receiver to the branch that does
897/// not declare the queried member, which drops every reference in the branch
898/// that does. Naming the identity keeps the target-preserving rule the forward
899/// resolver already applies (`unique_type_candidate_preserving_target`).
900fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
901    // Target-preserving lookup can reconcile logical declarations across files.
902    // A receiver must not borrow an owner from a header its caller cannot see.
903    ctx.spec
904        .owner
905        .as_ref()
906        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
907}
908
909const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
910
911fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
912    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
913}
914
915fn infer_type_from_value_with_budget(
916    node: Node<'_>,
917    ctx: &ScanCtx<'_>,
918    remaining_call_depth: usize,
919) -> Option<CppScanBinding> {
920    match node.kind() {
921        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
922            infer_cpp_initializer_binding(
923                &ctx.analyzer,
924                ctx.visibility,
925                ctx.file,
926                ctx.source,
927                node,
928                None,
929            )
930        }
931        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
932            &ctx.analyzer,
933            ctx.visibility,
934            ctx.file,
935            ctx.source,
936            node,
937            Some(&|receiver, source| {
938                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
939            }),
940        ),
941        "initializer_list" => None,
942        "identifier" => {
943            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
944            resolved
945                .as_precise()?
946                .iter()
947                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
948                .cloned()
949        }
950        _ => {
951            let text = node_text(node, ctx.source);
952            let name = normalize_cpp_type_name(text);
953            ctx.visibility
954                .resolve_type(ctx.file, &name)
955                .map(|unit| CppScanBinding::from_unit(unit, 0))
956        }
957    }
958}
959
960/// Whether a member named `identifier` can be spelled anywhere in this file's
961/// syntax.
962///
963/// Both member scans below -- [`maybe_record_method_hit`] and
964/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
965/// of a syntax node against the target's member name, so a file whose syntax
966/// never spells that name yields neither a proven nor an unproven hit. That
967/// makes this an exact admission test rather than an approximation: candidate
968/// discovery can drop such a file without narrowing what the query proves.
969///
970/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
971/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
972/// replacement, which
973/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
974/// sentinel parse, and an `ERROR` region -- so those admit on containment
975/// instead of on a token match. A `comment` is the one subtree no scan reads,
976/// so it is skipped.
977///
978/// Defined only for a plain identifier: [`name_matches_callable`] widens the
979/// comparison for an `operator` name to the `operator` token alone, and a
980/// destructor or conversion-function name is not spelled the way it is
981/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
982/// that condition.
983pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
984    debug_assert!(
985        cpp_member_is_spelled_at_references(identifier),
986        "member admission is defined only for a plain identifier: {identifier:?}"
987    );
988    let mut spelled = false;
989    walk_named_tree_preorder(root, true, |node| match node.kind() {
990        "comment" => WalkControl::SkipChildren,
991        "identifier"
992        | "field_identifier"
993        | "type_identifier"
994        | "namespace_identifier"
995        | "statement_identifier" => {
996            if node_text(node, source).trim() == identifier {
997                spelled = true;
998                WalkControl::Break
999            } else {
1000                WalkControl::SkipChildren
1001            }
1002        }
1003        "preproc_arg" | "ERROR" => {
1004            if node_text(node, source).contains(identifier) {
1005                spelled = true;
1006                WalkControl::Break
1007            } else {
1008                // The node's own text covers every descendant's bytes, so a
1009                // miss here is a miss for the whole subtree.
1010                WalkControl::SkipChildren
1011            }
1012        }
1013        _ => WalkControl::Continue,
1014    });
1015    spelled
1016}
1017
1018/// Whether every reference to a member named `identifier` must spell it.
1019///
1020/// An `operator` name is matched by [`name_matches_callable`] through the bare
1021/// `operator` token, and a destructor or conversion function is recorded under
1022/// a name its call sites do not write, so neither admits a spelling test.
1023pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1024    let mut characters = identifier.chars();
1025    characters
1026        .next()
1027        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1028        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1029        && !identifier.starts_with("operator")
1030}
1031
1032fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1033    match ctx.spec.kind {
1034        TargetKind::Type => maybe_record_type_hit(node, ctx),
1035        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1036        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1037        TargetKind::Method => maybe_record_method_hit(node, ctx),
1038        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1039        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1040        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1041    }
1042}
1043
1044fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1045    if node_text(node, ctx.source) != ctx.spec.member_name {
1046        return;
1047    }
1048    if is_ordinary_macro_reference_node(node) {
1049        if ctx.visibility.macro_binding_matches_target_declaration_at(
1050            ctx.file,
1051            &ctx.spec.member_name,
1052            node.start_byte(),
1053            ctx.spec.target.source(),
1054            &ctx.target_macro_declaration_bytes,
1055        ) {
1056            ctx.usages.note_raw_match();
1057            push_hit(node, ctx);
1058            return;
1059        }
1060        match ctx.visibility.resolve_ordinary_macro_reference(
1061            &ctx.analyzer,
1062            ctx.file,
1063            node,
1064            ctx.source,
1065        ) {
1066            OrdinaryMacroReferenceResolution::Resolved(unit)
1067                if ctx.target_group.contains(&unit) =>
1068            {
1069                ctx.usages.note_raw_match();
1070                push_hit(node, ctx);
1071            }
1072            OrdinaryMacroReferenceResolution::Ambiguous
1073                if ctx
1074                    .visibility
1075                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1076            {
1077                ctx.usages.note_raw_match();
1078                push_unproven_hit(node, ctx);
1079            }
1080            OrdinaryMacroReferenceResolution::Resolved(_)
1081            | OrdinaryMacroReferenceResolution::Ambiguous
1082            | OrdinaryMacroReferenceResolution::Missing => {}
1083        }
1084        return;
1085    }
1086    if !matches!(
1087        node.kind(),
1088        "identifier"
1089            | "field_identifier"
1090            | "type_identifier"
1091            | "namespace_identifier"
1092            | "preproc_arg"
1093    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1094        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1095            && parent
1096                .child_by_field_name("name")
1097                .is_some_and(|name| same_node(name, node))
1098    }) {
1099        return;
1100    }
1101    if ctx.visibility.macro_binding_matches_target_declaration_at(
1102        ctx.file,
1103        &ctx.spec.member_name,
1104        node.start_byte(),
1105        ctx.spec.target.source(),
1106        &ctx.target_macro_declaration_bytes,
1107    ) {
1108        ctx.usages.note_raw_match();
1109        push_hit(node, ctx);
1110    } else if ctx.visibility.macro_name_may_be_bound_at(
1111        ctx.file,
1112        &ctx.spec.member_name,
1113        node.start_byte(),
1114    ) && ctx
1115        .visibility
1116        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1117    {
1118        ctx.usages.note_raw_match();
1119        push_unproven_hit(node, ctx);
1120    }
1121}
1122
1123fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1124    let kind = node.kind();
1125    if ctx.uses_c_semantics
1126        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1127            .is_some()
1128    {
1129        return;
1130    }
1131    if kind == "preproc_arg" {
1132        maybe_record_object_macro_replacement_type_hits(node, ctx);
1133        return;
1134    }
1135    if let Some(type_node) = ctx
1136        .visibility
1137        .function_macro_type_argument(ctx.file, node, ctx.source)
1138    {
1139        if ctx
1140            .local_shadows
1141            .is_shadowed(node_text(type_node, ctx.source))
1142        {
1143            return;
1144        }
1145        if let LexicalTypeResolution::Resolved {
1146            unit, candidates, ..
1147        } = resolve_type_node_lexically_for_target(
1148            type_node,
1149            &ctx.analyzer,
1150            ctx.visibility,
1151            &ctx.ordinary_type_imports,
1152            ctx.file,
1153            ctx.source,
1154            &ctx.spec.target,
1155            Some(&ctx.lexical_scope_cache),
1156            ctx.recovered_sentinel_scope(type_node).as_deref(),
1157        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1158        {
1159            ctx.usages.note_raw_match();
1160            push_type_hit(type_node, ctx);
1161        }
1162        return;
1163    }
1164    let recovered_exported_class_base =
1165        matches!(
1166            kind,
1167            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1168        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1169    if kind == "field_declaration"
1170        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1171    {
1172        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1173        return;
1174    }
1175    if kind == "qualified_identifier"
1176        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1177    {
1178        // A member-pointer owner can itself end in a nested alias, as in
1179        // `type_identity<T>::type::*`. Resolving the complete owner
1180        // canonicalizes that alias to its underlying type and loses the alias
1181        // declaration that inverse lookup is targeting. Retain every
1182        // structurally proven qualifier component before asking for the
1183        // canonical owner type.
1184        if ctx
1185            .analyzer
1186            .type_alias_provider()
1187            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1188            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1189            && let Some(terminal) = owner.nodes.last().copied()
1190        {
1191            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1192                return;
1193            }
1194            if ctx.visibility.external_type_candidate_visible_in_context(
1195                &ctx.analyzer,
1196                ctx.file,
1197                &ctx.spec.target,
1198                terminal,
1199            ) || ctx
1200                .visibility
1201                .dependent_member_pointer_alias_visible_in_context(
1202                    &ctx.analyzer,
1203                    ctx.file,
1204                    &ctx.spec.target,
1205                    &owner.names,
1206                    terminal,
1207                )
1208            {
1209                ctx.usages.note_raw_match();
1210                push_type_hit(terminal, ctx);
1211            } else if ctx
1212                .visibility
1213                .is_physically_visible(ctx.file, &ctx.spec.target)
1214                && ctx.visibility.declaration_guards_compatible_at_reference(
1215                    &ctx.analyzer,
1216                    ctx.file,
1217                    &ctx.spec.target,
1218                    terminal,
1219                )
1220            {
1221                // The structured owner and terminal identify a candidate, but
1222                // do not establish its conditional availability.
1223                ctx.usages.note_raw_match();
1224                push_unproven_hit(terminal, ctx);
1225            }
1226            return;
1227        }
1228        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1229            ctx.usages.note_raw_match();
1230            for scope in scopes {
1231                push_type_hit(scope, ctx);
1232            }
1233            return;
1234        }
1235        return;
1236    }
1237    if kind == "pointer_expression"
1238        && let Some(value) = qualified_callable_value(node)
1239        && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1240    {
1241        ctx.usages.note_raw_match();
1242        match owner_hit {
1243            TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1244            TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1245        }
1246        return;
1247    }
1248    if kind == "call_expression" {
1249        maybe_record_direct_temporary_type_hit(node, ctx);
1250        return;
1251    }
1252    if !matches!(
1253        kind,
1254        "identifier"
1255            | "namespace_identifier"
1256            | "qualified_identifier"
1257            | "scoped_identifier"
1258            | "scoped_type_identifier"
1259            | "template_function"
1260            | "template_type"
1261            | "type_descriptor"
1262            | "type_identifier"
1263            | "using_declaration"
1264    ) {
1265        return;
1266    }
1267    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1268    let template_function_type = cpp_template_function_has_type_owner(node);
1269    let recovered_qualified_friend =
1270        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1271    if !recovered_type
1272        && !recovered_qualified_friend
1273        && !recovered_exported_class_base
1274        && matches!(
1275            kind,
1276            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1277        )
1278        && !type_reference_components_may_name_target(node, ctx)
1279    {
1280        return;
1281    }
1282    if ctx.ancestry.parent(node).is_some_and(|parent| {
1283        parent.kind() == "operator_cast"
1284            && parent
1285                .child_by_field_name("type")
1286                .is_some_and(|target| same_node(target, node))
1287    }) {
1288        return;
1289    }
1290    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1291        ctx.usages.note_raw_match();
1292        push_type_hit(hit, ctx);
1293        return;
1294    }
1295    if matches!(kind, "identifier" | "template_function")
1296        && parent_if_field(node, "call_expression", "function").is_some()
1297    {
1298        return;
1299    }
1300    if kind == "using_declaration" {
1301        let (resolution, type_node) =
1302            if let Some(type_node) = using_enum_declaration_type_node(node) {
1303                (
1304                    resolve_using_enum_declaration_owner(
1305                        node,
1306                        &ctx.analyzer,
1307                        ctx.visibility,
1308                        &ctx.ordinary_type_imports,
1309                        ctx.file,
1310                        ctx.source,
1311                    ),
1312                    type_node,
1313                )
1314            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1315                (
1316                    resolve_ordinary_using_declaration_owner(
1317                        node,
1318                        &ctx.analyzer,
1319                        ctx.visibility,
1320                        ctx.file,
1321                        ctx.source,
1322                    ),
1323                    type_node,
1324                )
1325            } else {
1326                return;
1327            };
1328        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1329            && same_visible_symbol(&unit, &ctx.spec.target)
1330        {
1331            ctx.usages.note_raw_match();
1332            push_type_hit(type_node, ctx);
1333        }
1334        return;
1335    }
1336    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1337        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1338            || local_type_name_shadows(node, ctx)
1339        {
1340            return;
1341        }
1342        if let LexicalTypeResolution::Resolved {
1343            unit, candidates, ..
1344        } = resolve_type_node_lexically_for_target(
1345            node,
1346            &ctx.analyzer,
1347            ctx.visibility,
1348            &ctx.ordinary_type_imports,
1349            ctx.file,
1350            ctx.source,
1351            &ctx.spec.target,
1352            Some(&ctx.lexical_scope_cache),
1353            ctx.recovered_sentinel_scope(node).as_deref(),
1354        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1355        {
1356            ctx.usages.note_raw_match();
1357            push_type_hit(node, ctx);
1358        }
1359        return;
1360    }
1361    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1362        // A missing `::` can put either the macro or the real type in the
1363        // recovered scope. Resolve both candidates against this inverse
1364        // target before choosing one: a unique match is routed through the
1365        // ordinary target-guided path, while two distinct matches are
1366        // ambiguous and must not invent a hit. With no target match, retain
1367        // the existing recovered-scope path below for its conservative
1368        // fallback behaviour.
1369        let mut matching = Vec::new();
1370        for candidate in [node, type_node] {
1371            if matching
1372                .iter()
1373                .any(|existing| same_node(*existing, candidate))
1374            {
1375                continue;
1376            }
1377            if let LexicalTypeResolution::Resolved {
1378                unit, candidates, ..
1379            } = resolve_type_node_lexically_for_target(
1380                candidate,
1381                &ctx.analyzer,
1382                ctx.visibility,
1383                &ctx.ordinary_type_imports,
1384                ctx.file,
1385                ctx.source,
1386                &ctx.spec.target,
1387                Some(&ctx.lexical_scope_cache),
1388                ctx.recovered_sentinel_scope(candidate).as_deref(),
1389            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1390            {
1391                matching.push(candidate);
1392            }
1393        }
1394        match matching.as_slice() {
1395            [candidate] if !same_node(*candidate, node) => {
1396                maybe_record_type_hit(*candidate, ctx);
1397                return;
1398            }
1399            [candidate] if same_node(*candidate, node) => {}
1400            [] => {}
1401            _ => return,
1402        }
1403    }
1404    if !recovered_type
1405        && !template_function_type
1406        && !matches!(
1407            kind,
1408            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1409        )
1410    {
1411        return;
1412    }
1413    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1414        components.len() == 1
1415            && !global
1416            && (local_type_name_shadows(node, ctx)
1417                || ctx
1418                    .analyzer
1419                    .type_alias_provider()
1420                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1421                    && nearer_type_name_shadows_structured_reference(node, ctx))
1422    }) {
1423        return;
1424    }
1425    if !local_class_target_reachable(node, ctx) {
1426        return;
1427    }
1428    if !recovered_type
1429        && !recovered_qualified_friend
1430        && !recovered_exported_class_base
1431        && ctx.ancestry.parent(node).is_some_and(|parent| {
1432            parent.kind() == "alias_declaration"
1433                && parent
1434                    .child_by_field_name("name")
1435                    .is_some_and(|name| same_node(name, node))
1436        })
1437    {
1438        return;
1439    }
1440    #[cfg(any(test, feature = "test-support"))]
1441    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1442    if !recovered_type
1443        && !recovered_qualified_friend
1444        && !recovered_exported_class_base
1445        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1446        && is_declaration_name(node)
1447        && let Some(owners) = out_of_line_member_definition_owner(
1448            &ctx.analyzer,
1449            ctx.visibility,
1450            ctx.file,
1451            ctx.source,
1452            node,
1453        )
1454    {
1455        ctx.usages.note_raw_match();
1456        let mut matched_owner = false;
1457        for (owner_node, owner) in owners.owners {
1458            if same_visible_symbol(&owner, &ctx.spec.target) {
1459                matched_owner = true;
1460                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1461            }
1462        }
1463        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1464            for scope in scopes {
1465                push_hit(scope, ctx);
1466            }
1467        } else if !matched_owner
1468            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1469        {
1470            push_unproven_hit(scope, ctx);
1471        }
1472        return;
1473    }
1474    if !recovered_type
1475        && !recovered_qualified_friend
1476        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1477        && is_declaration_name(node)
1478        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1479    {
1480        ctx.usages.note_raw_match();
1481        push_type_hit(owner, ctx);
1482        return;
1483    }
1484    // A qualified template-id is represented as a qualified_identifier whose
1485    // name child is the template_type. Resolve the complete qualified
1486    // reference from that inner template node below; handling the outer node
1487    // independently would either lose the qualifier or emit a duplicate,
1488    // wider hit range.
1489    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1490        && matches!(
1491            node.kind(),
1492            "qualified_identifier" | "scoped_type_identifier"
1493        )
1494        && node
1495            .child_by_field_name("name")
1496            .is_some_and(|name| name.kind() == "template_type")
1497    {
1498        return;
1499    }
1500    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1501        ctx.usages.note_raw_match();
1502        push_unproven_hit(scope, ctx);
1503        return;
1504    }
1505    if !recovered_type
1506        && !is_nested_type_node(node)
1507        && matches!(
1508            node.kind(),
1509            "qualified_identifier" | "scoped_type_identifier"
1510        )
1511        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1512    {
1513        ctx.usages.note_raw_match();
1514        for scope in scopes {
1515            push_type_hit(scope, ctx);
1516        }
1517        return;
1518    }
1519    if !recovered_type && is_nested_type_node(node) {
1520        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1521            ctx.usages.note_raw_match();
1522            push_type_hit(hit, ctx);
1523            return;
1524        }
1525        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1526            ctx.usages.note_raw_match();
1527            push_type_hit(hit, ctx);
1528            return;
1529        }
1530        // A concrete template specialization can be absent from the coarse
1531        // per-file component-name index: that index contains the primary name
1532        // while this recovered child may expose only the malformed template
1533        // leaf.  Resolve the complete enclosing template before applying the
1534        // component prefilter so an exact target candidate can prove this
1535        // reference.  Non-specialization targets retain the cheap gate.
1536        let nested_template = if node.kind() == "template_type" {
1537            Some(node)
1538        } else {
1539            ctx.ancestry.parent(node).filter(|parent| {
1540                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1541            })
1542        };
1543        // Concrete specializations need their nested template-id inspected.
1544        // An ordinary alias application does too when it is itself the scope
1545        // of a member-qualified type, because the enclosing node denotes the
1546        // member rather than the alias. In every other primary/alias shape the
1547        // enclosing structured type owns the hit range; descending would emit
1548        // a duplicate terminal subrange.
1549        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1550            let enclosing_qualified_type_owns_range =
1551                ctx.ancestry.parent(template).is_some_and(|parent| {
1552                    matches!(
1553                        parent.kind(),
1554                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1555                    ) && parent.child_by_field_name("name") == Some(template)
1556                });
1557            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1558                return false;
1559            };
1560            let Some(name) = template_reference_name_node(template) else {
1561                return false;
1562            };
1563            let alias_candidates = ctx
1564                .visibility
1565                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1566                .filter(|candidate| alias_provider.is_type_alias(candidate))
1567                .cloned()
1568                .collect::<Vec<_>>();
1569            let direct_target_alias_visible = !alias_candidates
1570                .iter()
1571                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1572                || ctx.visibility.external_type_candidate_visible_in_context(
1573                    &ctx.analyzer,
1574                    ctx.file,
1575                    &ctx.spec.target,
1576                    template,
1577                );
1578            !enclosing_qualified_type_owns_range
1579                && direct_target_alias_visible
1580                && template_reference_candidates_select_target(
1581                    template,
1582                    &alias_candidates,
1583                    &ctx.analyzer,
1584                    ctx.visibility,
1585                    ctx.file,
1586                    ctx.source,
1587                    &ctx.spec.target,
1588                )
1589        });
1590        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1591            return;
1592        }
1593        if nested_alias_qualifier {
1594            let template = nested_template.expect("a nested alias qualifier has a template node");
1595            ctx.usages.note_raw_match();
1596            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1597                .unwrap_or_else(|| type_reference_hit_node(template));
1598            push_type_hit(hit, ctx);
1599            return;
1600        }
1601        // For concrete specializations, the visible identifier index can
1602        // already contain the exact template spelling even when lexical
1603        // resolution rejects the recovered child (the child itself has no
1604        // argument list). Select the name as structured evidence;
1605        // `push_type_hit` expands it to the complete written template-id.
1606        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1607            && let Some(template) = nested_template
1608            && template_type_component_preserves_target(
1609                template,
1610                &ctx.visibility
1611                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1612                    .cloned()
1613                    .collect::<Vec<_>>(),
1614                ctx,
1615            )
1616        {
1617            ctx.usages.note_raw_match();
1618            let hit = template
1619                .child_by_field_name("name")
1620                .filter(|name| name.kind() == "type_identifier")
1621                .unwrap_or(template);
1622            push_type_hit(hit, ctx);
1623            return;
1624        }
1625        if let Some(template) = nested_template
1626            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1627        {
1628            ctx.usages.note_raw_match();
1629            let hit =
1630                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1631                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1632                        template
1633                            .child_by_field_name("name")
1634                            .filter(|name| name.kind() == "type_identifier")
1635                            .unwrap_or(template)
1636                    } else {
1637                        type_reference_hit_node(template)
1638                    }
1639                });
1640            push_type_hit(hit, ctx);
1641        }
1642        return;
1643    }
1644    if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1645        let direct_target = resolve_qualified_call_target(
1646            call,
1647            node,
1648            &ctx.analyzer,
1649            ctx.visibility,
1650            &ctx.ordinary_type_imports,
1651            ctx.file,
1652            ctx.source,
1653        );
1654        if matches!(direct_target, BareCallTargetResolution::Type(_))
1655            && let LexicalTypeResolution::Resolved {
1656                unit, candidates, ..
1657            } = resolve_type_node_lexically_for_target(
1658                node,
1659                &ctx.analyzer,
1660                ctx.visibility,
1661                &ctx.ordinary_type_imports,
1662                ctx.file,
1663                ctx.source,
1664                &ctx.spec.target,
1665                Some(&ctx.lexical_scope_cache),
1666                ctx.recovered_sentinel_scope(node).as_deref(),
1667            )
1668            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1669        {
1670            ctx.usages.note_raw_match();
1671            push_resolved_type_hit(node, &candidates, ctx);
1672        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1673            ctx.usages.note_raw_match();
1674            for scope in scopes {
1675                push_type_hit(scope, ctx);
1676            }
1677        } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1678            ctx.usages.note_raw_match();
1679            match owner_hit {
1680                TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1681                TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1682                    push_unproven_hit(scope, ctx)
1683                }
1684            }
1685        }
1686        return;
1687    }
1688    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1689        ctx.usages.note_raw_match();
1690        if proven {
1691            push_type_hit(hit, ctx);
1692        } else {
1693            push_unproven_hit(hit, ctx);
1694        }
1695        return;
1696    }
1697    if !recovered_type
1698        && !recovered_qualified_friend
1699        && !recovered_exported_class_base
1700        && is_declaration_name(node)
1701    {
1702        let mut matched_owner = false;
1703        if let Some(owners) = out_of_line_member_definition_owner(
1704            &ctx.analyzer,
1705            ctx.visibility,
1706            ctx.file,
1707            ctx.source,
1708            node,
1709        ) {
1710            for (owner_node, owner) in owners.owners {
1711                if same_visible_symbol(&owner, &ctx.spec.target) {
1712                    matched_owner = true;
1713                    ctx.usages.note_raw_match();
1714                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1715                }
1716            }
1717        }
1718        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1719            ctx.usages.note_raw_match();
1720            for scope in scopes {
1721                push_hit(scope, ctx);
1722            }
1723        } else if !matched_owner
1724            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1725        {
1726            ctx.usages.note_raw_match();
1727            push_unproven_hit(scope, ctx);
1728        }
1729        return;
1730    }
1731    let hit_node = node;
1732    let text = node_text(hit_node, ctx.source);
1733    let type_resolution = if hit_node.kind() == "template_type"
1734        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1735    {
1736        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1737            resolve_type_node_lexically_for_target(
1738                hit_node,
1739                &ctx.analyzer,
1740                ctx.visibility,
1741                &ctx.ordinary_type_imports,
1742                ctx.file,
1743                ctx.source,
1744                &ctx.spec.target,
1745                Some(&ctx.lexical_scope_cache),
1746                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1747            )
1748        })
1749    } else {
1750        resolve_type_node_lexically_for_target(
1751            hit_node,
1752            &ctx.analyzer,
1753            ctx.visibility,
1754            &ctx.ordinary_type_imports,
1755            ctx.file,
1756            ctx.source,
1757            &ctx.spec.target,
1758            Some(&ctx.lexical_scope_cache),
1759            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1760        )
1761    };
1762    // When the qualifier's template arguments decide the owner, the resolution
1763    // above already looked the member up in that owner. The target-guided
1764    // fallbacks below read the qualifier without its arguments and would
1765    // prove the primary template's member instead, so they do not run; an
1766    // owner the arguments cannot decide leaves the reference unproven
1767    // (#6902).
1768    match select_qualifier_owner(
1769        node,
1770        is_globally_qualified_cpp_name(node),
1771        &ctx.analyzer,
1772        ctx.visibility,
1773        &ctx.ordinary_type_imports,
1774        ctx.file,
1775        ctx.source,
1776    ) {
1777        QualifierOwnerSelection::Unchanged => {}
1778        QualifierOwnerSelection::Selected(_) => {
1779            if !matches!(
1780                &type_resolution,
1781                LexicalTypeResolution::Resolved { unit, candidates, .. }
1782                    if type_resolution_matches_target(node, unit, candidates, ctx)
1783            ) {
1784                return;
1785            }
1786        }
1787        QualifierOwnerSelection::Ambiguous => {
1788            // Only the member the terminal names depends on the undecided
1789            // owner; a qualifier or alias target keeps its ordinary handling.
1790            if let Some(terminal) = cpp_name_component_nodes(node)
1791                .and_then(|components| components.last().copied())
1792                .filter(|terminal| node_text(*terminal, ctx.source) == ctx.spec.target.identifier())
1793            {
1794                ctx.usages.note_raw_match();
1795                push_unproven_hit(terminal, ctx);
1796                return;
1797            }
1798        }
1799    }
1800    match type_resolution {
1801        LexicalTypeResolution::Resolved {
1802            unit, candidates, ..
1803        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1804            ctx.usages.note_raw_match();
1805            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1806                for scope in scopes {
1807                    push_type_hit(scope, ctx);
1808                }
1809            } else {
1810                push_resolved_type_hit(hit_node, &candidates, ctx);
1811            }
1812            return;
1813        }
1814        LexicalTypeResolution::Resolved {
1815            unit: _,
1816            candidates,
1817            ..
1818        } => {
1819            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1820                ctx.usages.note_raw_match();
1821                for scope in scopes {
1822                    push_type_hit(scope, ctx);
1823                }
1824            } else if let Some(hit) =
1825                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1826            {
1827                ctx.usages.note_raw_match();
1828                push_unproven_hit(hit, ctx);
1829            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1830                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1831            {
1832                ctx.usages.note_raw_match();
1833                push_type_hit(leaf, ctx);
1834            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1835                ctx.usages.note_raw_match();
1836                push_unproven_hit(leaf, ctx);
1837            }
1838            return;
1839        }
1840        LexicalTypeResolution::Ambiguous => {
1841            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1842                ctx.usages.note_raw_match();
1843                for scope in scopes {
1844                    push_type_hit(scope, ctx);
1845                }
1846            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1847                ctx.usages.note_raw_match();
1848                push_unproven_hit(leaf, ctx);
1849            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1850                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1851                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1852            {
1853                ctx.usages.note_raw_match();
1854                push_type_hit(leaf, ctx);
1855            } else if guarded_alias_alternatives_include_target(hit_node, ctx) {
1856                ctx.usages.note_raw_match();
1857                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1858            }
1859            return;
1860        }
1861        LexicalTypeResolution::Missing => {
1862            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1863                &ctx.analyzer,
1864                ctx.file,
1865                hit_node,
1866                ctx.source,
1867            ) && same_visible_symbol(&unit, &ctx.spec.target)
1868            {
1869                ctx.usages.note_raw_match();
1870                push_type_hit(hit_node, ctx);
1871                return;
1872            }
1873            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1874                ctx.usages.note_raw_match();
1875                push_unproven_hit(leaf, ctx);
1876                return;
1877            }
1878            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1879                ctx.usages.note_raw_match();
1880                push_unproven_hit(leaf, ctx);
1881                return;
1882            }
1883            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1884                ctx.usages.note_raw_match();
1885                push_type_hit(leaf, ctx);
1886                return;
1887            }
1888            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1889                hit_node,
1890                &ctx.analyzer,
1891                ctx.visibility,
1892                ctx.file,
1893                ctx.source,
1894                &ctx.spec.target,
1895            );
1896            let raw_matches = matches!(
1897                raw_resolution,
1898                LexicalTypeResolution::Resolved {
1899                    ref unit,
1900                    ref candidates,
1901                    ..
1902                } if type_resolution_identifies_unit_target(
1903                    hit_node,
1904                    unit,
1905                    candidates,
1906                    &ctx.spec.target,
1907                    ctx,
1908                )
1909            );
1910            if raw_matches
1911                || type_node_has_exact_target_identity_without_visibility(
1912                    hit_node,
1913                    &ctx.analyzer,
1914                    ctx.visibility,
1915                    ctx.file,
1916                    ctx.source,
1917                    &ctx.spec.target,
1918                )
1919            {
1920                ctx.usages.note_raw_match();
1921                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1922                return;
1923            }
1924        }
1925    }
1926    // A class-owned alias can be qualified by an owner made visible through a
1927    // namespace import, including an import in an earlier reopening of the
1928    // same unnamed namespace. Resolve that owner before requiring an
1929    // enclosing class for inherited nested-type lookup.
1930    if ctx
1931        .visibility
1932        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1933    {
1934        ctx.usages.note_raw_match();
1935        push_type_hit(type_reference_hit_node(hit_node), ctx);
1936        return;
1937    }
1938    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1939        ctx.usages.note_raw_match();
1940        for scope in scopes {
1941            push_type_hit(scope, ctx);
1942        }
1943        return;
1944    }
1945    if !name_mentions(text, &ctx.spec.member_name) {
1946        return;
1947    }
1948    ctx.usages.note_raw_match();
1949    if !ctx.visibility.external_type_candidate_visible_in_context(
1950        &ctx.analyzer,
1951        ctx.file,
1952        &ctx.spec.target,
1953        hit_node,
1954    ) {
1955        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1956        if type_reference_resolves_away_from_target(unproven, ctx) {
1957            return;
1958        }
1959        push_unproven_hit(unproven, ctx);
1960    }
1961}
1962
1963fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1964    for reference in object_macro_replacement_type_references(node, ctx.source) {
1965        for component_count in 1..=reference.components.len() {
1966            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1967                node,
1968                &reference.components[..component_count],
1969                reference.global,
1970                &ctx.analyzer,
1971                ctx.visibility,
1972                &ctx.ordinary_type_imports,
1973                ctx.file,
1974                ctx.source,
1975                &ctx.spec.target,
1976                false,
1977                Some(&ctx.lexical_scope_cache),
1978            );
1979            let matches_target = match resolution {
1980                LexicalTypeResolution::Resolved {
1981                    unit, candidates, ..
1982                } => {
1983                    same_visible_symbol(&unit, &ctx.spec.target)
1984                        || candidates
1985                            .iter()
1986                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1987                }
1988                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1989                    &ctx.analyzer,
1990                    ctx.visibility,
1991                    ctx.file,
1992                    &reference.components[..component_count],
1993                )
1994                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1995                LexicalTypeResolution::Ambiguous => false,
1996            };
1997            if !matches_target {
1998                continue;
1999            }
2000            let range = &reference.component_ranges[component_count - 1];
2001            ctx.usages.note_raw_match();
2002            push_type_hit_range(node, range.start, range.end, ctx);
2003        }
2004    }
2005}
2006
2007fn target_guided_compatible_foreign_import_type_leaf<'tree>(
2008    node: Node<'tree>,
2009    ctx: &ScanCtx<'_>,
2010) -> Option<Node<'tree>> {
2011    let (components, global) = type_reference_components(node, ctx.source)?;
2012    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
2013        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
2014            node,
2015            &ctx.analyzer,
2016            ctx.visibility,
2017            ctx.file,
2018            ctx.source,
2019            false,
2020            false,
2021            Some(&ctx.lexical_scope_cache),
2022        ) {
2023            LexicalScopeResolution::Resolved(scope) => Some(scope),
2024            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
2025        }
2026    })?;
2027    let OrdinaryTypeImportResolution::Resolved { target, .. } =
2028        compatible_foreign_type_import_resolution(
2029            node,
2030            &components,
2031            global,
2032            &ctx.analyzer,
2033            ctx.visibility,
2034            &ctx.ordinary_type_imports,
2035            ctx.file,
2036            ctx.source,
2037            &lexical_scope,
2038            Some(&ctx.spec.target),
2039        )
2040    else {
2041        return None;
2042    };
2043    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2044}
2045
2046/// Resolve `Owner::Nested` when complete type lookup cannot express the owner
2047/// as its first qualified component. This occurs when a using-directive
2048/// imports `Owner` and when inherited-type lookup injects it. Resolve the
2049/// owner prefix independently, then return the terminal node after that owner
2050/// proves the target's direct nested declaration; hit insertion expands it to
2051/// the complete written type.
2052fn target_guided_nested_type_terminal_hit<'tree>(
2053    node: Node<'tree>,
2054    ctx: &ScanCtx<'_>,
2055) -> Option<Node<'tree>> {
2056    // Returns a node of the caller's tree lifetime, which the context-scoped
2057    // ancestry index cannot name; this is one question per call.
2058    let qualified = node.parent().filter(|parent| {
2059        matches!(
2060            parent.kind(),
2061            "qualified_identifier" | "scoped_type_identifier"
2062        ) && parent.child_by_field_name("name") == Some(node)
2063    })?;
2064    let mut complete = qualified;
2065    while let Some(parent) = complete.parent().filter(|parent| {
2066        matches!(
2067            parent.kind(),
2068            "qualified_identifier" | "scoped_type_identifier"
2069        )
2070    }) {
2071        complete = parent;
2072    }
2073    let owner = qualified_owner_components(complete, ctx.source)?;
2074
2075    if let LexicalTypeResolution::Resolved {
2076        unit, candidates, ..
2077    } = resolve_type_node_lexically_for_target(
2078        complete,
2079        &ctx.analyzer,
2080        ctx.visibility,
2081        &ctx.ordinary_type_imports,
2082        ctx.file,
2083        ctx.source,
2084        &ctx.spec.target,
2085        Some(&ctx.lexical_scope_cache),
2086        ctx.recovered_sentinel_scope(complete).as_deref(),
2087    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2088        && ctx
2089            .analyzer
2090            .parent_of(&ctx.spec.target)
2091            .is_some_and(|parent| parent.is_class())
2092        && !ctx
2093            .analyzer
2094            .type_alias_provider()
2095            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2096        && is_namespace_scope_type_reference(complete)
2097        && namespace_import_can_name_nested_target(complete, ctx)
2098    {
2099        return Some(node);
2100    }
2101    // The owner-spelling paths below compare the qualifier's names without
2102    // its template arguments, which only names the right owner when those
2103    // arguments select the primary template (#6902).
2104    if !matches!(
2105        select_qualifier_owner(
2106            complete,
2107            owner.global,
2108            &ctx.analyzer,
2109            ctx.visibility,
2110            &ctx.ordinary_type_imports,
2111            ctx.file,
2112            ctx.source,
2113        ),
2114        QualifierOwnerSelection::Unchanged
2115    ) {
2116        return None;
2117    }
2118
2119    let target_owner = ctx
2120        .analyzer
2121        .parent_of(&ctx.spec.target)
2122        .filter(CodeUnit::is_class);
2123    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2124        let target_components = canonical_cpp_scope_components(target_owner);
2125        if owner.global {
2126            target_components == owner.names
2127        } else {
2128            target_components.ends_with(&owner.names)
2129        }
2130    });
2131    let target_member_visible = |target_owner: &CodeUnit| {
2132        ctx.visibility
2133            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2134            .into_iter()
2135            .any(|candidate| {
2136                same_visible_symbol(candidate, &ctx.spec.target)
2137                    && (ctx
2138                        .visibility
2139                        .external_type_candidate_guard_compatible_in_context(
2140                            &ctx.analyzer,
2141                            ctx.file,
2142                            candidate,
2143                            complete,
2144                        )
2145                        || (ctx
2146                            .visibility
2147                            .is_exhaustive_same_fqn_type_declaration_family(
2148                                &ctx.analyzer,
2149                                ctx.file,
2150                                candidate,
2151                            )
2152                            && ctx.visibility.external_type_candidate_visible_in_context(
2153                                &ctx.analyzer,
2154                                ctx.file,
2155                                candidate,
2156                                complete,
2157                            )))
2158            })
2159    };
2160
2161    if owner_spelling_can_name_target
2162        && (is_namespace_scope_type_reference(complete)
2163            || (is_compound_type_reference(complete)
2164                && namespace_import_can_name_nested_target(complete, ctx)))
2165        && let Some(target_owner) = target_owner.as_ref()
2166        && let LexicalTypeResolution::Resolved { unit, .. } =
2167            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2168                complete,
2169                &owner.names,
2170                owner.global,
2171                &ctx.analyzer,
2172                ctx.visibility,
2173                &ctx.ordinary_type_imports,
2174                ctx.file,
2175                ctx.source,
2176                Some(&ctx.lexical_scope_cache),
2177            )
2178        && ctx
2179            .visibility
2180            .same_template_owner_identity(&unit, target_owner)
2181        && target_member_visible(target_owner)
2182    {
2183        return Some(node);
2184    }
2185
2186    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2187    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2188    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2189        &ctx.analyzer,
2190        ctx.file,
2191        &owner.names,
2192        owner.global,
2193        &lexical_scope,
2194    );
2195    let owner_unit = match owner_resolution {
2196        LexicalTypeResolution::Resolved { unit, .. } => unit,
2197        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2198            ctx.visibility.inherited_injected_class_owner(
2199                &ctx.analyzer,
2200                ctx.file,
2201                &enclosing_owner,
2202                owner.names.first()?,
2203            )?
2204        }
2205        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2206    };
2207    let name = node_text(node, ctx.source);
2208    ctx.visibility
2209        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2210        .into_iter()
2211        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2212        .then_some(complete)
2213}
2214
2215fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2216    // Namespace-scope nested classes need a terminal edge when the complete
2217    // qualified node is the only structured evidence (as in #2213). In
2218    // parameters, fields, and callable bodies the enclosing type node already
2219    // owns the exact range; adding its terminal would duplicate that edge.
2220    let mut current = node.parent();
2221    while let Some(parent) = current {
2222        match parent.kind() {
2223            "call_expression" | "new_expression" => return true,
2224            "parameter_declaration"
2225            | "optional_parameter_declaration"
2226            | "field_declaration"
2227            | "function_definition"
2228            | "function_declarator"
2229            | "class_specifier"
2230            | "struct_specifier"
2231            | "union_specifier"
2232            | "compound_statement" => return false,
2233            "declaration" => {
2234                let mut cursor = parent.walk();
2235                if parent
2236                    .named_children(&mut cursor)
2237                    .any(|child| child.kind() == "function_declarator")
2238                {
2239                    return false;
2240                }
2241            }
2242            _ => {}
2243        }
2244        current = parent.parent();
2245    }
2246    true
2247}
2248
2249fn is_compound_type_reference(node: Node<'_>) -> bool {
2250    let mut current = node.parent();
2251    while let Some(parent) = current {
2252        match parent.kind() {
2253            "compound_statement" => return true,
2254            "namespace_definition" | "translation_unit" => return false,
2255            _ => current = parent.parent(),
2256        }
2257    }
2258    false
2259}
2260
2261fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2262    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2263        return false;
2264    };
2265    effective_using_bindings_for_name(
2266        ctx.visibility,
2267        &ctx.ordinary_type_imports,
2268        ctx.file,
2269        node,
2270        ctx.source,
2271        target_owner.identifier(),
2272    )
2273    .iter()
2274    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2275}
2276
2277/// Recover the class-template qualifier in a dependent leading return type of
2278/// an out-of-line member definition.
2279///
2280/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2281/// `Owner`: one in the return type and one in the callable declarator. The
2282/// latter already has authoritative indexed owner resolution. Reuse that
2283/// structured owner evidence for the former when both template names agree;
2284/// resolving `Owner<T>::Alias` as one type would instead select terminal
2285/// `Alias` and discard the qualifier.
2286fn out_of_line_dependent_return_template_owner<'tree>(
2287    node: Node<'tree>,
2288    ctx: &ScanCtx<'_>,
2289) -> Option<Node<'tree>> {
2290    if node.kind() != "template_type" {
2291        return None;
2292    }
2293    let template_name = template_reference_name_node(node)?;
2294    let mut scope = node;
2295    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2296        matches!(
2297            parent.kind(),
2298            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2299        ) && parent.child_by_field_name("name") == Some(scope)
2300    }) {
2301        scope = parent;
2302    }
2303    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2304        matches!(
2305            parent.kind(),
2306            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2307        ) && parent.child_by_field_name("scope") == Some(scope)
2308    })?;
2309    let mut function = ctx.ancestry.parent(qualified);
2310    let function = loop {
2311        let candidate = function?;
2312        if candidate.kind() == "function_definition" {
2313            break candidate;
2314        }
2315        function = ctx.ancestry.parent(candidate);
2316    };
2317    let return_type = function.child_by_field_name("type")?;
2318    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2319        return None;
2320    }
2321    let declarator_name = function
2322        .child_by_field_name("declarator")
2323        .and_then(declarator_name_node)?;
2324    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2325        return None;
2326    }
2327    let resolved_owner_matches = out_of_line_member_definition_owner(
2328        &ctx.analyzer,
2329        ctx.visibility,
2330        ctx.file,
2331        ctx.source,
2332        declarator_name,
2333    )
2334    .is_some_and(|owners| {
2335        owners
2336            .owners
2337            .iter()
2338            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2339    });
2340    let indexed_owner_matches =
2341        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2342    let target_guided_owner_matches =
2343        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2344    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2345        .then_some(template_name)
2346}
2347
2348/// Use the indexed callable identity when a malformed namespace sentinel
2349/// prevents lexical lookup of an out-of-line template owner.
2350fn indexed_out_of_line_template_owner_hit<'tree>(
2351    node: Node<'tree>,
2352    ctx: &ScanCtx<'_>,
2353) -> Option<Node<'tree>> {
2354    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2355        || !is_declaration_name(node)
2356    {
2357        return None;
2358    }
2359    let mut function = ctx.ancestry.parent(node);
2360    let function = loop {
2361        let candidate = function?;
2362        if candidate.kind() == "function_definition" {
2363            break candidate;
2364        }
2365        function = ctx.ancestry.parent(candidate);
2366    };
2367    if function
2368        .child_by_field_name("declarator")
2369        .and_then(declarator_name_node)
2370        != Some(node)
2371    {
2372        return None;
2373    }
2374    let target = physically_visible_type_target(ctx)?;
2375    let qualified = qualified_owner_components(node, ctx.source)?;
2376    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2377        return None;
2378    }
2379    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2380        != canonical_cpp_scope_components(target)
2381    {
2382        return None;
2383    }
2384    let owner = qualified.nodes.last().copied()?;
2385    let template = if owner.kind() == "template_type" {
2386        owner
2387    } else {
2388        owner
2389            .parent()
2390            .filter(|parent| parent.kind() == "template_type")?
2391    };
2392    template_reference_name_node(template)
2393}
2394
2395fn push_guarded_owner_hit(
2396    owner_node: Node<'_>,
2397    owner: &CodeUnit,
2398    reference: Node<'_>,
2399    ctx: &mut ScanCtx<'_>,
2400) {
2401    if ctx
2402        .visibility
2403        .external_type_candidate_guard_compatible_in_context(
2404            &ctx.analyzer,
2405            ctx.file,
2406            owner,
2407            reference,
2408        )
2409    {
2410        push_hit(owner_node, ctx);
2411    } else {
2412        push_unproven_hit(owner_node, ctx);
2413    }
2414}
2415
2416/// Preserve a direct template-alias reference when a macro namespace sentinel
2417/// makes tree-sitter drop the first source path component.
2418fn target_guided_alias_template_reference<'tree>(
2419    node: Node<'tree>,
2420    ctx: &ScanCtx<'_>,
2421) -> Option<(Node<'tree>, bool)> {
2422    let alias_provider = ctx.analyzer.type_alias_provider()?;
2423    if !matches!(
2424        node.kind(),
2425        "qualified_identifier" | "scoped_type_identifier"
2426    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2427    {
2428        return None;
2429    }
2430    cpp_template_reference_arguments(node, ctx.source)?;
2431    let (components, global) = type_reference_components(node, ctx.source)?;
2432    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2433        return None;
2434    }
2435    let target = physically_visible_type_target(ctx)?;
2436    let target_components = canonical_cpp_scope_components(target);
2437    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2438    let path_matches = if global {
2439        components == target_components
2440            || (!parser_namespace.is_empty()
2441                && target_components.starts_with(&parser_namespace)
2442                && target_components[parser_namespace.len()..] == components)
2443    } else {
2444        let lexical_scope = match enclosing_lexical_scope_components(
2445            node,
2446            &ctx.analyzer,
2447            ctx.visibility,
2448            ctx.file,
2449            ctx.source,
2450        ) {
2451            LexicalScopeResolution::Resolved(scope) => scope,
2452            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2453        };
2454        lexical_component_tiers(&components, false, &lexical_scope)
2455            .any(|scope| scope == target_components)
2456    };
2457    let scoped_candidates = ctx
2458        .visibility
2459        .visible_identifier_candidates(ctx.file, target.identifier())
2460        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2461        .collect::<Vec<_>>();
2462    if !path_matches
2463        || scoped_candidates.is_empty()
2464        || scoped_candidates
2465            .iter()
2466            .any(|candidate| !same_visible_symbol(candidate, target))
2467        || !ctx.visibility.structured_alias_primary_preserves_target(
2468            &ctx.analyzer,
2469            ctx.file,
2470            target,
2471            target,
2472        )
2473    {
2474        return None;
2475    }
2476    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2477        &ctx.analyzer,
2478        ctx.file,
2479        target,
2480        node,
2481    );
2482    Some((node, proven))
2483}
2484
2485/// Tree-sitter can split a macro-qualified member return type into a phantom
2486/// field followed by the real function definition.  The declaration visitor
2487/// discards that phantom field, but its declarator token remains a semantic
2488/// type reference. Resolve it from the recovered or indexed class scope so an
2489/// enclosing-class alias remains distinguishable from same-spelled siblings.
2490fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2491    let name = node_text(return_type, ctx.source);
2492    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2493        return;
2494    }
2495    if physically_visible_type_target(ctx).is_some()
2496        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2497        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2498        && ctx.visibility.external_type_candidate_visible_in_context(
2499            &ctx.analyzer,
2500            ctx.file,
2501            &ctx.spec.target,
2502            return_type,
2503        )
2504    {
2505        ctx.usages.note_raw_match();
2506        push_type_hit(return_type, ctx);
2507        return;
2508    }
2509    let Some(scope) = ctx
2510        .recovered_sentinel_scope(return_type)
2511        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2512    else {
2513        return;
2514    };
2515    let components = [name.to_string()];
2516    let resolution = ctx.visibility.resolve_type_components_lexically(
2517        &ctx.analyzer,
2518        ctx.file,
2519        &components,
2520        false,
2521        &scope,
2522    );
2523    if let LexicalTypeResolution::Resolved {
2524        unit, candidates, ..
2525    } = resolution
2526        && (same_visible_symbol(&unit, &ctx.spec.target)
2527            || candidates
2528                .iter()
2529                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2530    {
2531        ctx.usages.note_raw_match();
2532        push_type_hit(return_type, ctx);
2533    }
2534}
2535
2536/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2537/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2538/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2539/// than a type node; ordinary type-reference traversal therefore skips it.
2540fn member_pointer_owner_components<'tree>(
2541    node: Node<'tree>,
2542    source: &str,
2543) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2544    if node.kind() != "qualified_identifier" {
2545        return None;
2546    }
2547    let declarator = node.child_by_field_name("name")?;
2548    // brokk-tree-sitter-cpp 0.24.7 names a member pointer with the pointer
2549    // declarator of its context: `pointer_declarator` in declarations,
2550    // parameters and fields, `pointer_type_declarator` in typedefs, and
2551    // `abstract_pointer_declarator` in `R (Owner::*)(...)`.
2552    if !matches!(
2553        declarator.kind(),
2554        "pointer_declarator" | "pointer_type_declarator" | "abstract_pointer_declarator"
2555    ) {
2556        return None;
2557    }
2558    let scope = node.child_by_field_name("scope")?;
2559    // Keep this check entirely structural.  C and C++ share the
2560    // `qualified_identifier` node shape for some recovered declarations, but
2561    // only the C++ member-pointer form has an actual `::` grammar child
2562    // between the owner scope and pointer declarator.  Looking at the source
2563    // slice here would make a recovered C declarator look like a C++ owner
2564    // merely because its bytes happen to contain the same punctuation.
2565    let mut saw_scope = false;
2566    let has_scope_separator = (0..node.child_count()).any(|index| {
2567        let Some(child) = node.child(index) else {
2568            return false;
2569        };
2570        if same_node(child, scope) {
2571            saw_scope = true;
2572            return false;
2573        }
2574        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2575    });
2576    if !has_scope_separator {
2577        return None;
2578    }
2579    let mut nodes = cpp_name_component_nodes(scope)?;
2580    let mut outer = node;
2581    while let Some(parent) = outer.parent()
2582        && parent.kind() == "qualified_identifier"
2583        && parent.child_by_field_name("name") == Some(outer)
2584    {
2585        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2586        prefix.append(&mut nodes);
2587        nodes = prefix;
2588        outer = parent;
2589    }
2590    let names = nodes
2591        .iter()
2592        .map(|component| node_text(*component, source).to_string())
2593        .collect();
2594    Some((
2595        QualifiedOwnerComponents {
2596            nodes,
2597            names,
2598            global: is_globally_qualified_cpp_name(outer),
2599        },
2600        outer,
2601    ))
2602}
2603
2604fn member_pointer_alias_owner_prefix_matches(
2605    node: Node<'_>,
2606    owner: &QualifiedOwnerComponents<'_>,
2607    ctx: &ScanCtx<'_>,
2608) -> bool {
2609    let Some((_, owner_prefix)) = owner.names.split_last() else {
2610        return false;
2611    };
2612    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2613        return false;
2614    };
2615    let recovered_scope = ctx.recovered_sentinel_scope(node);
2616    let resolution = if let Some(recovered_scope) = recovered_scope {
2617        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2618            node,
2619            owner_prefix,
2620            owner.global,
2621            &ctx.analyzer,
2622            ctx.visibility,
2623            &ctx.ordinary_type_imports,
2624            ctx.file,
2625            ctx.source,
2626            &parent,
2627            false,
2628            &recovered_scope,
2629        )
2630    } else {
2631        resolve_type_components_lexically_at_for_target_with_scope_cache(
2632            node,
2633            owner_prefix,
2634            owner.global,
2635            &ctx.analyzer,
2636            ctx.visibility,
2637            &ctx.ordinary_type_imports,
2638            ctx.file,
2639            ctx.source,
2640            &parent,
2641            false,
2642            Some(&ctx.lexical_scope_cache),
2643        )
2644    };
2645    let LexicalTypeResolution::Resolved {
2646        unit, candidates, ..
2647    } = resolution
2648    else {
2649        return false;
2650    };
2651    same_member_pointer_owner_identity(&unit, &parent)
2652        || candidates
2653            .iter()
2654            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2655}
2656
2657fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2658    same_visible_symbol(left, right)
2659        || (left.kind() == right.kind()
2660            && left.fq_name() == right.fq_name()
2661            && left.source() == right.source())
2662}
2663
2664/// Resolve a template type nested in a qualified identifier against a concrete
2665/// type target. The target-guided lexical path intentionally applies a
2666/// structured candidate prefilter; that prefilter cannot see a partial
2667/// specialization until the template arguments have selected it. Resolve the
2668/// complete qualified primary first, then apply the parsed arguments and
2669/// retain the result only when it is the requested target.
2670fn resolve_nested_template_type_for_target(
2671    node: Node<'_>,
2672    ctx: &ScanCtx<'_>,
2673) -> Option<LexicalTypeResolution> {
2674    let reference_node = node
2675        .parent()
2676        .filter(|parent| {
2677            parent.kind() == "qualified_identifier"
2678                && parent.child_by_field_name("name") == Some(node)
2679        })
2680        .unwrap_or(node);
2681    let target_resolution = resolve_type_node_lexically_for_target(
2682        reference_node,
2683        &ctx.analyzer,
2684        ctx.visibility,
2685        &ctx.ordinary_type_imports,
2686        ctx.file,
2687        ctx.source,
2688        &ctx.spec.target,
2689        Some(&ctx.lexical_scope_cache),
2690        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2691    );
2692    if let LexicalTypeResolution::Resolved {
2693        unit, candidates, ..
2694    } = target_resolution
2695        && template_reference_candidates_select_target(
2696            reference_node,
2697            &candidates,
2698            &ctx.analyzer,
2699            ctx.visibility,
2700            ctx.file,
2701            ctx.source,
2702            &ctx.spec.target,
2703        )
2704    {
2705        return Some(LexicalTypeResolution::Resolved {
2706            unit,
2707            components: Vec::new(),
2708            candidates,
2709        });
2710    }
2711
2712    let normal_resolution = resolve_type_node_lexically(
2713        reference_node,
2714        &ctx.analyzer,
2715        ctx.visibility,
2716        &ctx.ordinary_type_imports,
2717        ctx.file,
2718        ctx.source,
2719    );
2720    let LexicalTypeResolution::Resolved {
2721        unit,
2722        components,
2723        candidates,
2724    } = normal_resolution
2725    else {
2726        return None;
2727    };
2728    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2729    let specialized = ctx
2730        .visibility
2731        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2732        .ok()
2733        .unwrap_or(unit);
2734    (same_visible_symbol(&specialized, &ctx.spec.target)
2735        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2736    .then_some(LexicalTypeResolution::Resolved {
2737        unit: specialized,
2738        components,
2739        candidates,
2740    })
2741}
2742
2743fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2744    let mut reference = node;
2745    while let Some(parent) = ctx.ancestry.parent(reference) {
2746        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2747            && parent.child_by_field_name("name") == Some(reference)
2748            || matches!(
2749                parent.kind(),
2750                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2751            ) && (parent.child_by_field_name("scope") == Some(reference)
2752                || parent.child_by_field_name("name") == Some(reference));
2753        if !owns_component {
2754            break;
2755        }
2756        reference = parent;
2757    }
2758    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2759        return false;
2760    };
2761    if !global
2762        && components.len() > 1
2763        && let Some(alias_type) = local_receiver_alias_type_node(reference, &components[0], ctx)
2764    {
2765        let Ok(Some(owner)) = resolve_receiver_type_node(alias_type, ctx) else {
2766            return false;
2767        };
2768        let mut redirected = canonical_cpp_scope_components(&owner);
2769        redirected.extend_from_slice(&components[1..]);
2770        return type_reference_component_list_may_name_target(
2771            &redirected,
2772            true,
2773            &ctx.analyzer,
2774            ctx.visibility,
2775            ctx.file,
2776            &ctx.spec.target,
2777            &ctx.type_reference_component_names,
2778        );
2779    }
2780    type_reference_component_list_may_name_target(
2781        &components,
2782        global,
2783        &ctx.analyzer,
2784        ctx.visibility,
2785        ctx.file,
2786        &ctx.spec.target,
2787        &ctx.type_reference_component_names,
2788    )
2789}
2790
2791fn type_reference_component_list_may_name_target(
2792    components: &[String],
2793    global: bool,
2794    analyzer: &CppGraphSource<'_>,
2795    visibility: &VisibilityIndex<'_>,
2796    file: &ProjectFile,
2797    target: &CodeUnit,
2798    reference_component_names: &HashSet<String>,
2799) -> bool {
2800    if components.iter().any(|component| {
2801        visibility.type_reference_component_directly_names_target(component, target)
2802    }) {
2803        return true;
2804    }
2805    if components.iter().any(|component| {
2806        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2807    }) {
2808        return true;
2809    }
2810    if components.len() > 1 {
2811        return visibility.qualified_alias_reference_may_reach_target(
2812            analyzer, file, components, global, target,
2813        );
2814    }
2815    components
2816        .iter()
2817        .any(|component| reference_component_names.contains(component))
2818}
2819
2820/// Fast structured admission before a type scan builds its parent and lexical
2821/// indexes. Most inverse candidates merely include the target's header and do
2822/// not contain a reference spelling; the full stateful scan has no work in
2823/// those files. Only maximal grammar type names are tested so a common nested
2824/// alias terminal such as `type` is considered together with its owner.
2825fn file_may_reference_type_target(
2826    root: Node<'_>,
2827    source: &str,
2828    analyzer: &CppGraphSource<'_>,
2829    visibility: &VisibilityIndex<'_>,
2830    file: &ProjectFile,
2831    target: &CodeUnit,
2832    reference_component_names: &HashSet<String>,
2833) -> bool {
2834    visit_file_type_reference_spellings(root, source, |components, global| {
2835        type_reference_component_list_may_name_target(
2836            components,
2837            global,
2838            analyzer,
2839            visibility,
2840            file,
2841            target,
2842            reference_component_names,
2843        )
2844    })
2845}
2846
2847fn visit_file_type_reference_spellings(
2848    root: Node<'_>,
2849    source: &str,
2850    mut visit: impl FnMut(&[String], bool) -> bool,
2851) -> bool {
2852    let mut stopped = false;
2853    walk_named_tree_preorder(root, true, |node| {
2854        if node.kind() == "comment" {
2855            return WalkControl::SkipChildren;
2856        }
2857        if node.kind() == "preproc_arg" {
2858            stopped = object_macro_replacement_type_references(node, source)
2859                .into_iter()
2860                .any(|reference| visit(&reference.components, reference.global));
2861            return if stopped {
2862                WalkControl::Break
2863            } else {
2864                WalkControl::SkipChildren
2865            };
2866        }
2867        if node.kind() == "field_declaration"
2868            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2869        {
2870            let components = [node_text(return_type, source).to_string()];
2871            stopped = visit(&components, false);
2872            if stopped {
2873                return WalkControl::Break;
2874            }
2875        }
2876        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2877            for candidate in [node, type_node] {
2878                let Some((components, global)) = type_reference_components(candidate, source)
2879                else {
2880                    continue;
2881                };
2882                stopped = visit(&components, global);
2883                if stopped {
2884                    return WalkControl::Break;
2885                }
2886            }
2887        }
2888        let Some((components, global)) = type_reference_components(node, source) else {
2889            return WalkControl::Continue;
2890        };
2891        if node.parent().is_some_and(|parent| {
2892            (matches!(parent.kind(), "template_type" | "template_function")
2893                && parent.child_by_field_name("name") == Some(node))
2894                || (matches!(
2895                    parent.kind(),
2896                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2897                ) && (parent.child_by_field_name("scope") == Some(node)
2898                    || parent.child_by_field_name("name") == Some(node)))
2899        }) {
2900            return WalkControl::Continue;
2901        }
2902        stopped = visit(&components, global);
2903        if stopped {
2904            WalkControl::Break
2905        } else {
2906            WalkControl::Continue
2907        }
2908    });
2909    stopped
2910}
2911
2912/// Whether this file decides the reference against the scan target.
2913///
2914/// The last resort of type admission has no structured resolution left: it
2915/// only knows that the spelling mentions the target's name and that the target
2916/// itself is not visible here. That is undecidable when nothing else declares
2917/// the name, but it is a proven negative when the file already makes another
2918/// declaration of the same name visible, because unqualified lookup binds the
2919/// reference to that declaration and the include closure never reaches the
2920/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2921/// the same question before it resolves; asking it again here keeps the
2922/// inverse from admitting a site that forward resolution has already decided
2923/// (#2916).
2924fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2925    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2926        return false;
2927    };
2928    let terminal = components
2929        .last()
2930        .expect("type reference components are non-empty");
2931    if terminal != ctx.spec.target.identifier() {
2932        return false;
2933    }
2934    // A concrete specialization is selected from the template arguments, not
2935    // from the visible primary name, so the name-level candidate set cannot
2936    // decide it. `resolve_type_node_lexically_for_target` makes the same
2937    // exception before its own structured prefilter.
2938    if cpp_template_reference_arguments(node, ctx.source).is_some()
2939        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2940    {
2941        return false;
2942    }
2943    // One C++ type reached through a forward declaration in one header and its
2944    // definition in another is two CodeUnits with different signatures and one
2945    // qualified name. A file that sees either of them sees the target's type,
2946    // so only a candidate under a different qualified name decides the
2947    // reference against it. This is what separates the decoy in an unrelated
2948    // namespace from a redeclaration of the target itself.
2949    if ctx
2950        .visibility
2951        .visible_identifier_candidates(ctx.file, terminal)
2952        .any(|candidate| {
2953            candidate.kind() == ctx.spec.target.kind()
2954                && candidate.fq_name() == ctx.spec.target.fq_name()
2955        })
2956    {
2957        return false;
2958    }
2959    !ctx.visibility
2960        .structured_type_reference_may_resolve_to_target(
2961            &ctx.analyzer,
2962            ctx.file,
2963            std::slice::from_ref(terminal),
2964            false,
2965            &[],
2966            &ctx.spec.target,
2967        )
2968}
2969
2970fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2971    let Some(scope) = node.child_by_field_name("scope") else {
2972        return false;
2973    };
2974    let mut pending = vec![scope];
2975    while let Some(candidate) = pending.pop() {
2976        if candidate.kind() == "template_type" {
2977            return true;
2978        }
2979        if matches!(
2980            candidate.kind(),
2981            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2982        ) {
2983            if let Some(scope) = candidate.child_by_field_name("scope") {
2984                pending.push(scope);
2985            }
2986            if let Some(name) = candidate.child_by_field_name("name") {
2987                pending.push(name);
2988            }
2989        }
2990    }
2991    false
2992}
2993
2994/// Whether an undecided reference to a conditional alias family keeps the
2995/// target as one of its alternatives. Guards that select no arm leave every
2996/// compatible arm, and the type it names, a candidate rather than proof
2997/// (#6902).
2998fn guarded_alias_alternatives_include_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2999    let Some((components, global)) = type_reference_components(node, ctx.source) else {
3000        return false;
3001    };
3002    // The arms are candidates whose guards need not hold here, so look them up
3003    // without the in-context visibility filter that would drop them.
3004    let lexical_scope = if global {
3005        Vec::new()
3006    } else {
3007        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
3008            node,
3009            &ctx.analyzer,
3010            ctx.visibility,
3011            ctx.file,
3012            ctx.source,
3013            true,
3014            false,
3015            Some(&ctx.lexical_scope_cache),
3016        ) {
3017            LexicalScopeResolution::Resolved(scope) => scope,
3018            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return false,
3019        }
3020    };
3021    let LexicalTypeResolution::Resolved { candidates, .. } = ctx
3022        .visibility
3023        .resolve_type_components_lexically_for_forward(
3024            &ctx.analyzer,
3025            ctx.file,
3026            &components,
3027            global,
3028            &lexical_scope,
3029        )
3030    else {
3031        return false;
3032    };
3033    let Some(GuardedAliasSelection::Alternatives(alternatives)) = ctx
3034        .visibility
3035        .guarded_alias_selection_at_reference(&ctx.analyzer, ctx.file, node, &candidates)
3036    else {
3037        return false;
3038    };
3039    alternatives
3040        .iter()
3041        .chain(&candidates)
3042        .any(|unit| same_visible_symbol(unit, &ctx.spec.target))
3043}
3044
3045fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
3046    ctx.target_group.iter().find(|target| {
3047        same_logical_symbol(target, &ctx.spec.target)
3048            && ctx.visibility.is_physically_visible(ctx.file, target)
3049    })
3050}
3051
3052/// Preserve the terminal token of a resolver-proven alias construction.
3053/// Ordinary qualified types and template aliases retain their full type range.
3054/// Alias identity and the construction context are proven by the resolver.
3055fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
3056    if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
3057        push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
3058    } else {
3059        push_type_hit(type_reference_hit_node(node), ctx);
3060    }
3061}
3062
3063fn resolved_alias_terminal_node<'tree>(
3064    node: Node<'tree>,
3065    candidates: &[CodeUnit],
3066    ctx: &ScanCtx<'_>,
3067) -> Option<Node<'tree>> {
3068    let [candidate] = candidates else {
3069        return None;
3070    };
3071    let alias_provider = ctx.analyzer.type_alias_provider()?;
3072    let candidate_is_alias = alias_provider.is_type_alias(candidate)
3073        && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
3074    let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
3075        && same_visible_symbol(candidate, &ctx.spec.target);
3076    if !(candidate_is_alias || target_is_alias) {
3077        return None;
3078    }
3079    if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
3080        return None;
3081    }
3082    let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
3083        node,
3084        &ctx.spec.target,
3085        &ctx.analyzer,
3086        ctx.visibility,
3087        ctx.file,
3088        ctx.source,
3089    );
3090    if reference
3091        != Some(
3092            crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
3093        )
3094    {
3095        return None;
3096    }
3097    cpp_name_component_nodes(node)?.last().copied()
3098}
3099
3100fn target_guided_missing_direct_temporary_type<'tree>(
3101    function: Node<'tree>,
3102    ctx: &ScanCtx<'_>,
3103) -> Option<Node<'tree>> {
3104    let target = physically_visible_type_target(ctx)?;
3105    let component_nodes = cpp_name_component_nodes(function)?;
3106    let terminal = component_nodes.last().copied()?;
3107    if node_text(terminal, ctx.source) != target.identifier() {
3108        return None;
3109    }
3110    let components = component_nodes
3111        .iter()
3112        .map(|component| node_text(*component, ctx.source).to_string())
3113        .collect::<Vec<_>>();
3114    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
3115    indexed_scope_matches_target_name(
3116        &indexed_scope,
3117        &components,
3118        is_globally_qualified_cpp_name(function),
3119        target,
3120    )
3121    .then_some(terminal)
3122}
3123
3124fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
3125    let Some(function) = call.child_by_field_name("function") else {
3126        return;
3127    };
3128    if !matches!(
3129        function.kind(),
3130        "identifier"
3131            | "type_identifier"
3132            | "template_function"
3133            | "template_type"
3134            | "qualified_identifier"
3135            | "scoped_identifier"
3136            | "scoped_type_identifier"
3137    ) {
3138        return;
3139    }
3140    if !type_reference_components_may_name_target(function, ctx) {
3141        return;
3142    }
3143    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3144        ctx.usages.note_raw_match();
3145        for scope in scopes {
3146            push_type_hit(scope, ctx);
3147        }
3148        return;
3149    }
3150    let terminal = function_terminal_node(function);
3151    let name = node_text(terminal, ctx.source);
3152    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3153        return;
3154    }
3155    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3156        match resolve_declaring_member_owner(
3157            &ctx.analyzer,
3158            ctx.visibility,
3159            ctx.file,
3160            &enclosing_owner,
3161            name,
3162        ) {
3163            EnclosingMemberOwnerResolution::Owner(owner)
3164                if matches!(
3165                    ctx.visibility
3166                        .visible_member_for_owner_name(ctx.file, &owner, name,),
3167                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3168                ) =>
3169            {
3170                return;
3171            }
3172            EnclosingMemberOwnerResolution::Ambiguous => return,
3173            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3174        }
3175    }
3176
3177    // A type alias used as a direct temporary (`result_type(value)`) is parsed
3178    // as an ordinary identifier call. Callable lookup can be ambiguous when
3179    // parser recovery flattens a namespace or same-spelled aliases are
3180    // visible from sibling distributions. An exact enclosing class owner
3181    // proves the member alias without treating the call as an arbitrary name;
3182    // retain the shadow and declaration-visibility guards above and below.
3183    if ctx
3184        .analyzer
3185        .type_alias_provider()
3186        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3187        && name == ctx.spec.target.identifier()
3188        && physically_visible_type_target(ctx).is_some()
3189        && !local_type_name_shadows(function, ctx)
3190        && type_alias_owner_matches_structured_reference(function, ctx)
3191        && ctx.visibility.external_type_candidate_visible_in_context(
3192            &ctx.analyzer,
3193            ctx.file,
3194            &ctx.spec.target,
3195            function,
3196        )
3197    {
3198        ctx.usages.note_raw_match();
3199        push_type_hit(terminal, ctx);
3200        return;
3201    }
3202
3203    let call_resolution = resolve_qualified_call_target(
3204        call,
3205        function,
3206        &ctx.analyzer,
3207        ctx.visibility,
3208        &ctx.ordinary_type_imports,
3209        ctx.file,
3210        ctx.source,
3211    );
3212    match call_resolution {
3213        BareCallTargetResolution::Type(unit) => {
3214            if same_visible_symbol(&unit, &ctx.spec.target) {
3215                ctx.usages.note_raw_match();
3216                push_type_hit(function, ctx);
3217                return;
3218            }
3219        }
3220        BareCallTargetResolution::FreeFunctions(units)
3221            if units.iter().all(|unit| {
3222                unit.fq_name() == ctx.spec.target.fq_name()
3223                    && ctx
3224                        .visibility
3225                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
3226            }) => {}
3227        BareCallTargetResolution::Ambiguous => {
3228            push_unproven_hit(function, ctx);
3229            return;
3230        }
3231        BareCallTargetResolution::FreeFunctions(_)
3232        | BareCallTargetResolution::UnprovenFreeFunctions(_)
3233        | BareCallTargetResolution::CallableShadow => return,
3234        // Generated `.c` includes can leave ordinary callable resolution with
3235        // no active callable even when target-preserving type resolution can
3236        // prove the constructor's class. Let the structured type fallback
3237        // below make that decision.
3238        BareCallTargetResolution::Missing => {}
3239    }
3240    let target_resolution = resolve_type_node_lexically_for_target(
3241        function,
3242        &ctx.analyzer,
3243        ctx.visibility,
3244        &ctx.ordinary_type_imports,
3245        ctx.file,
3246        ctx.source,
3247        &ctx.spec.target,
3248        Some(&ctx.lexical_scope_cache),
3249        ctx.recovered_sentinel_scope(function).as_deref(),
3250    );
3251    match target_resolution {
3252        LexicalTypeResolution::Resolved {
3253            unit, candidates, ..
3254        } if same_visible_symbol(&unit, &ctx.spec.target)
3255            || candidates
3256                .iter()
3257                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3258        {
3259            ctx.usages.note_raw_match();
3260            push_type_hit(function, ctx);
3261        }
3262        LexicalTypeResolution::Missing => {
3263            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3264                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3265            {
3266                ctx.usages.note_raw_match();
3267                push_type_hit(hit, ctx);
3268            }
3269        }
3270        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3271    }
3272}
3273
3274pub enum BareCallTargetResolution {
3275    Type(CodeUnit),
3276    FreeFunctions(Vec<CodeUnit>),
3277    UnprovenFreeFunctions(Vec<CodeUnit>),
3278    CallableShadow,
3279    Ambiguous,
3280    Missing,
3281}
3282
3283pub enum BlockUsingCallTargetResolution {
3284    Target(BareCallTargetResolution),
3285    Unindexed(Vec<String>),
3286    Ambiguous,
3287}
3288
3289#[allow(clippy::too_many_arguments)]
3290fn resolve_qualified_call_target(
3291    call: Node<'_>,
3292    function: Node<'_>,
3293    analyzer: &CppGraphSource<'_>,
3294    visibility: &VisibilityIndex,
3295    ordinary_type_imports: &OrdinaryTypeImportCell,
3296    file: &ProjectFile,
3297    source: &str,
3298) -> BareCallTargetResolution {
3299    if matches!(function.kind(), "identifier" | "template_function") {
3300        return resolve_bare_call_target(
3301            call,
3302            function,
3303            analyzer,
3304            visibility,
3305            ordinary_type_imports,
3306            file,
3307            source,
3308        );
3309    }
3310    if !matches!(
3311        function.kind(),
3312        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3313    ) {
3314        return BareCallTargetResolution::Missing;
3315    }
3316    let terminal = function_terminal_node(function);
3317    let name = node_text(terminal, source);
3318    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3319        return BareCallTargetResolution::Missing;
3320    };
3321    components.push(name.to_string());
3322    let qualified_name = components.join("::");
3323    let type_resolution = resolve_type_node_lexically(
3324        function,
3325        analyzer,
3326        visibility,
3327        ordinary_type_imports,
3328        file,
3329        source,
3330    );
3331    let same_name_resolves_to_type = matches!(
3332        &type_resolution,
3333        LexicalTypeResolution::Resolved {
3334            unit,
3335            candidates,
3336            ..
3337        } if cpp_name_for(unit) == qualified_name
3338            || candidates
3339                .iter()
3340                .any(|candidate| cpp_name_for(candidate) == qualified_name)
3341    );
3342    let has_explicit_template_arguments =
3343        cpp_template_reference_arguments(function, source).is_some();
3344    let candidates = visibility
3345        .visible_identifier_candidates(file, name)
3346        .filter(|candidate| {
3347            candidate.is_function()
3348                && type_owner_of(analyzer, candidate).is_none()
3349                && !(same_name_resolves_to_type
3350                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3351                        || has_explicit_template_arguments
3352                            && visibility
3353                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
3354                && cpp_name_for(candidate) == qualified_name
3355                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3356        })
3357        .cloned()
3358        .collect::<Vec<_>>();
3359    if !candidates.is_empty() {
3360        return resolve_callable_candidates(
3361            candidates,
3362            visibility.call_arity_evidence(file, call, source).exact(),
3363            call.start_byte(),
3364            analyzer,
3365            visibility,
3366            file,
3367        );
3368    }
3369    match type_resolution {
3370        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3371        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3372        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3373    }
3374}
3375
3376fn ordinary_binding_lookup_targets(
3377    binding: &OrdinaryTypeImport,
3378    active_bindings: &[&OrdinaryTypeImport],
3379    name: &str,
3380) -> Vec<Vec<String>> {
3381    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3382        return Vec::new();
3383    };
3384    let mut targets = Vec::new();
3385    match binding.target {
3386        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3387        EffectiveUsingTarget::Namespace { .. } => {
3388            let mut stack = vec![qualified.clone()];
3389            let mut visited = HashSet::default();
3390            while let Some(namespace) = stack.pop() {
3391                if !visited.insert(namespace.clone()) {
3392                    continue;
3393                }
3394                let mut target = namespace.clone();
3395                target.push(name.to_string());
3396                targets.push(target);
3397                stack.extend(active_bindings.iter().filter_map(|candidate| {
3398                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3399                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3400                    .then(|| candidate.resolved_target_components.clone())
3401                    .flatten()
3402                }));
3403            }
3404        }
3405    }
3406    targets
3407}
3408
3409fn binding_free_function_candidates(
3410    binding: &OrdinaryTypeImport,
3411    active_bindings: &[&OrdinaryTypeImport],
3412    analyzer: &CppGraphSource<'_>,
3413    visibility: &VisibilityIndex<'_>,
3414    file: &ProjectFile,
3415    name: &str,
3416    reference_byte: usize,
3417) -> Vec<CodeUnit> {
3418    ordinary_binding_lookup_targets(binding, active_bindings, name)
3419        .into_iter()
3420        .flat_map(|target| {
3421            let qualified_name = target.join("::");
3422            visibility
3423                .visible_identifier_candidates(file, name)
3424                .filter(move |candidate| {
3425                    candidate.is_function()
3426                        && type_owner_of(analyzer, candidate).is_none()
3427                        && cpp_name_for(candidate) == qualified_name
3428                        && visibility.declaration_visible_at(
3429                            analyzer,
3430                            file,
3431                            candidate,
3432                            reference_byte,
3433                        )
3434                })
3435                .cloned()
3436        })
3437        .collect()
3438}
3439
3440/// Collapse the candidates unqualified lookup found to one entry per logical
3441/// callable, keeping the first spelling of each.
3442///
3443/// A header declaration and its out-of-line body spell one parameter type
3444/// differently often enough that the persisted signature strings disagree, so
3445/// the string triple alone reports one C++ declaration as an overload set and
3446/// an unproven argument count then turns it into ambiguity.
3447/// `same_logical_callable` answers the string question first and resolves the
3448/// written parameter names only when the strings differ (#2010).
3449fn dedupe_callable_candidates(
3450    candidates: &mut Vec<CodeUnit>,
3451    analyzer: &CppGraphSource<'_>,
3452    visibility: &VisibilityIndex<'_>,
3453) {
3454    let mut deduped = Vec::with_capacity(candidates.len());
3455    for candidate in candidates.drain(..) {
3456        if !deduped
3457            .iter()
3458            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3459        {
3460            deduped.push(candidate);
3461        }
3462    }
3463    *candidates = deduped;
3464}
3465
3466fn resolve_callable_candidates(
3467    candidates: Vec<CodeUnit>,
3468    call_arity: Option<usize>,
3469    reference_byte: usize,
3470    analyzer: &CppGraphSource<'_>,
3471    visibility: &VisibilityIndex<'_>,
3472    file: &ProjectFile,
3473) -> BareCallTargetResolution {
3474    let mut candidates = candidates;
3475    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3476    if candidates.is_empty() {
3477        return BareCallTargetResolution::Missing;
3478    }
3479    let Some(call_arity) = call_arity else {
3480        // An unproven argument count cannot create ambiguity where lookup found
3481        // exactly one name binding: there is nothing to be ambiguous between.
3482        // C has no overloading at all, and a lone C++ candidate is the only
3483        // declaration unqualified lookup reached, so arity cannot pick another
3484        // one (#1811). Keeping it unproven discarded the proven candidate and
3485        // answered `ambiguous` with an empty definition list.
3486        if candidates.len() == 1 {
3487            return BareCallTargetResolution::FreeFunctions(candidates);
3488        }
3489        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3490    };
3491    let applicable = candidates
3492        .into_iter()
3493        .filter(|candidate| {
3494            visibility
3495                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3496                .is_some_and(|arity| arity.accepts(call_arity))
3497        })
3498        .collect::<Vec<_>>();
3499    if applicable.is_empty() {
3500        BareCallTargetResolution::CallableShadow
3501    } else {
3502        BareCallTargetResolution::FreeFunctions(applicable)
3503    }
3504}
3505
3506fn deduplicate_type_import_candidates(
3507    candidates: Vec<(CodeUnit, Vec<String>)>,
3508) -> Vec<(CodeUnit, Vec<String>)> {
3509    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3510    for candidate in candidates {
3511        if !logical
3512            .iter()
3513            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3514        {
3515            logical.push(candidate);
3516        }
3517    }
3518    logical
3519}
3520
3521fn resolve_direct_type_candidates(
3522    candidates: Vec<(CodeUnit, Vec<String>)>,
3523    analyzer: &CppGraphSource<'_>,
3524    visibility: &VisibilityIndex<'_>,
3525    file: &ProjectFile,
3526) -> BareCallTargetResolution {
3527    let logical = deduplicate_type_import_candidates(candidates);
3528    let [(target, components)] = logical.as_slice() else {
3529        return if logical.is_empty() {
3530            BareCallTargetResolution::Missing
3531        } else {
3532            BareCallTargetResolution::Ambiguous
3533        };
3534    };
3535    match visibility
3536        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3537    {
3538        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3539        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3540        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3541    }
3542}
3543
3544/// Resolve a direct using-declaration in the nearest concrete block before
3545/// class-member lookup. A block declaration such as `using std::swap;` adds
3546/// that name to the block scope and hides a same-named member. If the imported
3547/// target is not indexed, retain its structured path as boundary evidence.
3548#[allow(clippy::too_many_arguments)]
3549pub fn resolve_block_using_call_target(
3550    call: Node<'_>,
3551    function: Node<'_>,
3552    analyzer: &CppGraphSource<'_>,
3553    visibility: &VisibilityIndex<'_>,
3554    ordinary_type_imports: &OrdinaryTypeImportCell,
3555    file: &ProjectFile,
3556    source: &str,
3557) -> Option<BlockUsingCallTargetResolution> {
3558    if !matches!(function.kind(), "identifier" | "template_function") {
3559        return None;
3560    }
3561    let name = node_text(function_terminal_node(function), source);
3562    if name.is_empty() {
3563        return None;
3564    }
3565    let bindings = effective_using_bindings_for_name(
3566        visibility,
3567        ordinary_type_imports,
3568        file,
3569        function,
3570        source,
3571        name,
3572    );
3573    let block_bindings = bindings
3574        .iter()
3575        .filter(|binding| {
3576            binding.namespace_scope.is_none()
3577                && binding.block_scope
3578                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3579        })
3580        .collect::<Vec<_>>();
3581    if block_bindings.is_empty() {
3582        return None;
3583    }
3584    let lexical_scope =
3585        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3586            LexicalScopeResolution::Resolved(scope) => scope,
3587            LexicalScopeResolution::Ambiguous => {
3588                return Some(BlockUsingCallTargetResolution::Ambiguous);
3589            }
3590            LexicalScopeResolution::Missing => return None,
3591        };
3592    let reference_guards = preprocessor_guard_environment(function, source);
3593    let active = block_bindings
3594        .into_iter()
3595        .filter(|binding| {
3596            effective_using_binding_active(
3597                binding,
3598                function,
3599                &lexical_scope,
3600                reference_guards.as_ref(),
3601                visibility,
3602                file,
3603            )
3604        })
3605        .collect::<Vec<_>>();
3606    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3607    let at_tier = active
3608        .into_iter()
3609        .filter(|binding| binding.scope_depth == depth)
3610        .collect::<Vec<_>>();
3611    let callable_candidates = at_tier
3612        .iter()
3613        .flat_map(|binding| {
3614            binding_free_function_candidates(
3615                binding,
3616                &[],
3617                analyzer,
3618                visibility,
3619                file,
3620                name,
3621                call.start_byte(),
3622            )
3623        })
3624        .collect::<Vec<_>>();
3625    if !callable_candidates.is_empty() {
3626        return Some(BlockUsingCallTargetResolution::Target(
3627            resolve_callable_candidates(
3628                callable_candidates,
3629                visibility.call_arity_evidence(file, call, source).exact(),
3630                call.start_byte(),
3631                analyzer,
3632                visibility,
3633                file,
3634            ),
3635        ));
3636    }
3637    let type_candidates = at_tier
3638        .iter()
3639        .flat_map(|binding| {
3640            binding_type_candidates(
3641                binding,
3642                &[],
3643                analyzer,
3644                visibility,
3645                file,
3646                name,
3647                None,
3648                call.start_byte(),
3649            )
3650        })
3651        .collect::<Vec<_>>();
3652    if !type_candidates.is_empty() {
3653        return Some(BlockUsingCallTargetResolution::Target(
3654            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3655        ));
3656    }
3657
3658    let mut unindexed = Vec::new();
3659    for binding in at_tier {
3660        let Some(components) = binding.resolved_target_components.as_ref() else {
3661            continue;
3662        };
3663        if !unindexed.contains(components) {
3664            unindexed.push(components.clone());
3665        }
3666    }
3667    match unindexed.as_slice() {
3668        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3669        [] => None,
3670        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3671    }
3672}
3673
3674#[allow(clippy::too_many_arguments)]
3675pub fn resolve_bare_call_target(
3676    call: Node<'_>,
3677    function: Node<'_>,
3678    analyzer: &CppGraphSource<'_>,
3679    visibility: &VisibilityIndex<'_>,
3680    ordinary_type_imports: &OrdinaryTypeImportCell,
3681    file: &ProjectFile,
3682    source: &str,
3683) -> BareCallTargetResolution {
3684    if !matches!(function.kind(), "identifier" | "template_function") {
3685        return BareCallTargetResolution::Missing;
3686    }
3687    let terminal = function_terminal_node(function);
3688    let name = node_text(terminal, source);
3689    if name.is_empty() {
3690        return BareCallTargetResolution::Missing;
3691    }
3692    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3693    let lexical_scope =
3694        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3695            LexicalScopeResolution::Resolved(scope) => scope,
3696            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3697            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3698        };
3699    let type_resolution = resolve_type_node_lexically(
3700        function,
3701        analyzer,
3702        visibility,
3703        ordinary_type_imports,
3704        file,
3705        source,
3706    );
3707    let type_components = match &type_resolution {
3708        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3709        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3710    };
3711    let direct_type_resolution = visibility.resolve_type_components_lexically(
3712        analyzer,
3713        file,
3714        &[name.to_string()],
3715        false,
3716        &lexical_scope,
3717    );
3718    let direct_type_components = match &direct_type_resolution {
3719        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3720        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3721    };
3722    let has_explicit_template_arguments =
3723        cpp_template_reference_arguments(function, source).is_some();
3724    let bindings = effective_using_bindings_for_name(
3725        visibility,
3726        ordinary_type_imports,
3727        file,
3728        function,
3729        source,
3730        name,
3731    );
3732    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3733    // step re-descends from the root (#1927): with no bindings both filters
3734    // below select nothing, so the environment is never consulted.
3735    let function_guards = if bindings.is_empty() {
3736        None
3737    } else {
3738        preprocessor_guard_environment(function, source)
3739    };
3740    let active_bindings = bindings
3741        .iter()
3742        .filter(|binding| {
3743            effective_using_binding_active(
3744                binding,
3745                function,
3746                &lexical_scope,
3747                function_guards.as_ref(),
3748                visibility,
3749                file,
3750            )
3751        })
3752        .collect::<Vec<_>>();
3753    let transitive_bindings = bindings
3754        .iter()
3755        .filter(|binding| {
3756            effective_using_binding_guards_active(
3757                binding,
3758                function.start_byte(),
3759                function_guards.as_ref(),
3760                visibility,
3761                file,
3762            ) && (binding.namespace_scope.is_some()
3763                || (binding.scope_start <= function.start_byte()
3764                    && function.end_byte() <= binding.scope_end))
3765        })
3766        .collect::<Vec<_>>();
3767    let mut concrete_depths = active_bindings
3768        .iter()
3769        .filter(|binding| binding.namespace_scope.is_none())
3770        .map(|binding| binding.scope_depth)
3771        .collect::<Vec<_>>();
3772    concrete_depths.sort_unstable();
3773    concrete_depths.dedup();
3774    for depth in concrete_depths.into_iter().rev() {
3775        let at_tier = active_bindings
3776            .iter()
3777            .copied()
3778            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3779        let direct = at_tier
3780            .clone()
3781            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3782            .flat_map(|binding| {
3783                binding_free_function_candidates(
3784                    binding,
3785                    &transitive_bindings,
3786                    analyzer,
3787                    visibility,
3788                    file,
3789                    name,
3790                    call.start_byte(),
3791                )
3792            })
3793            .collect::<Vec<_>>();
3794        if !direct.is_empty() {
3795            return resolve_callable_candidates(
3796                direct,
3797                call_arity,
3798                call.start_byte(),
3799                analyzer,
3800                visibility,
3801                file,
3802            );
3803        }
3804        let direct_types = at_tier
3805            .clone()
3806            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3807            .flat_map(|binding| {
3808                binding_type_candidates(
3809                    binding,
3810                    &transitive_bindings,
3811                    analyzer,
3812                    visibility,
3813                    file,
3814                    name,
3815                    None,
3816                    call.start_byte(),
3817                )
3818            })
3819            .collect::<Vec<_>>();
3820        if !direct_types.is_empty() {
3821            // `resolve_direct_type_candidates` never consults the argument
3822            // count: it answers the one type the name binds to, or reports the
3823            // competing types. An unknown count therefore cannot make this
3824            // ambiguous (#1812).
3825            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3826        }
3827        let directives = at_tier
3828            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3829            .flat_map(|binding| {
3830                binding_free_function_candidates(
3831                    binding,
3832                    &transitive_bindings,
3833                    analyzer,
3834                    visibility,
3835                    file,
3836                    name,
3837                    call.start_byte(),
3838                )
3839            })
3840            .collect::<Vec<_>>();
3841        if !directives.is_empty() {
3842            return resolve_callable_candidates(
3843                directives,
3844                call_arity,
3845                call.start_byte(),
3846                analyzer,
3847                visibility,
3848                file,
3849            );
3850        }
3851    }
3852    for prefix_len in (0..=lexical_scope.len()).rev() {
3853        let mut qualified = lexical_scope[..prefix_len].to_vec();
3854        qualified.push(name.to_string());
3855        let same_name_resolves_to_type = direct_type_components
3856            .is_some_and(|components| components == qualified.as_slice())
3857            || type_components.is_some_and(|components| components == qualified.as_slice());
3858        let mut direct = visibility
3859            .visible_identifier_candidates(file, name)
3860            .filter(|candidate| {
3861                candidate.is_function()
3862                    && type_owner_of(analyzer, candidate).is_none()
3863                    && !(same_name_resolves_to_type
3864                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3865                            || has_explicit_template_arguments
3866                                && visibility
3867                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3868                    && cpp_name_for(candidate) == qualified.join("::")
3869                    && if analyzer.reference_uses_c_semantics(file) {
3870                        visibility.declaration_visible_for_c_forward_call(
3871                            analyzer,
3872                            file,
3873                            candidate,
3874                            call.start_byte(),
3875                        )
3876                    } else {
3877                        visibility.declaration_visible_at(
3878                            analyzer,
3879                            file,
3880                            candidate,
3881                            call.start_byte(),
3882                        )
3883                    }
3884            })
3885            .cloned()
3886            .collect::<Vec<_>>();
3887        let at_tier = active_bindings.iter().copied().filter(|binding| {
3888            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3889        });
3890        direct.extend(
3891            at_tier
3892                .clone()
3893                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3894                .flat_map(|binding| {
3895                    binding_free_function_candidates(
3896                        binding,
3897                        &transitive_bindings,
3898                        analyzer,
3899                        visibility,
3900                        file,
3901                        name,
3902                        call.start_byte(),
3903                    )
3904                }),
3905        );
3906        if !direct.is_empty() {
3907            return resolve_callable_candidates(
3908                direct,
3909                call_arity,
3910                call.start_byte(),
3911                analyzer,
3912                visibility,
3913                file,
3914            );
3915        }
3916        let mut direct_types = at_tier
3917            .clone()
3918            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3919            .flat_map(|binding| {
3920                binding_type_candidates(
3921                    binding,
3922                    &transitive_bindings,
3923                    analyzer,
3924                    visibility,
3925                    file,
3926                    name,
3927                    None,
3928                    call.start_byte(),
3929                )
3930            })
3931            .collect::<Vec<_>>();
3932        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3933            && let LexicalTypeResolution::Resolved {
3934                unit, components, ..
3935            } = &direct_type_resolution
3936        {
3937            direct_types.push((unit.clone(), components.clone()));
3938        }
3939        if !direct_types.is_empty() {
3940            // `resolve_direct_type_candidates` never consults the argument
3941            // count: it answers the one type the name binds to, or reports the
3942            // competing types. An unknown count therefore cannot make this
3943            // ambiguous (#1812).
3944            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3945        }
3946        let directives = at_tier
3947            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3948            .flat_map(|binding| {
3949                binding_free_function_candidates(
3950                    binding,
3951                    &transitive_bindings,
3952                    analyzer,
3953                    visibility,
3954                    file,
3955                    name,
3956                    call.start_byte(),
3957                )
3958            })
3959            .collect::<Vec<_>>();
3960        if !directives.is_empty() {
3961            return resolve_callable_candidates(
3962                directives,
3963                call_arity,
3964                call.start_byte(),
3965                analyzer,
3966                visibility,
3967                file,
3968            );
3969        }
3970        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3971            // The lexical type resolution below already answers with the single
3972            // type, or with its own ambiguity verdict; the argument count adds
3973            // nothing to that decision (#1812).
3974            return match type_resolution {
3975                LexicalTypeResolution::Resolved { unit, .. } => {
3976                    BareCallTargetResolution::Type(unit)
3977                }
3978                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3979                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3980            };
3981        }
3982    }
3983    // Every lookup tier is exhausted: no callable and no type candidate was
3984    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
3985    // at all, and its early return in get_definition preempted the same-file
3986    // macro fallback - so a call to a macro defined in the referencing file
3987    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
3988    // resolve once an unresolvable include made the argument count unknown.
3989    // A no-candidate outcome is Missing, which is what makes the fallback
3990    // reachable (#1812).
3991    match type_resolution {
3992        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3993        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3994        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3995    }
3996}
3997
3998fn static_qualifier_type_scopes<'tree>(
3999    node: Node<'tree>,
4000    ctx: &ScanCtx<'_>,
4001) -> Option<Vec<Node<'tree>>> {
4002    if !matches!(
4003        node.kind(),
4004        "qualified_identifier" | "scoped_type_identifier"
4005    ) {
4006        return None;
4007    }
4008    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
4009    // this root contains every structured component needed for prefix lookup.
4010    debug_assert!(!is_nested_type_node(node));
4011    let qualified = qualified_owner_components(node, ctx.source)?;
4012    static_qualifier_type_scopes_for_components(node, qualified, ctx)
4013}
4014
4015fn static_qualifier_type_scopes_for_components<'tree>(
4016    node: Node<'tree>,
4017    qualified: QualifiedOwnerComponents<'tree>,
4018    ctx: &ScanCtx<'_>,
4019) -> Option<Vec<Node<'tree>>> {
4020    if !qualified.global
4021        && let Some(name) = qualified.names.first()
4022        && let Some(alias_type) = local_receiver_alias_type_node(node, name, ctx)
4023    {
4024        return match resolve_receiver_type_node(alias_type, ctx) {
4025            Ok(Some(owner)) if same_visible_symbol(&owner, &ctx.spec.target) => {
4026                Some(vec![qualified.nodes[0]])
4027            }
4028            _ => None,
4029        };
4030    }
4031    let mut matches = Vec::new();
4032    let mut inherited_injected_name_is_shadowed = false;
4033    for component_count in 1..=qualified.names.len() {
4034        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
4035            node,
4036            &qualified.names[..component_count],
4037            qualified.global,
4038            &ctx.analyzer,
4039            ctx.visibility,
4040            &ctx.ordinary_type_imports,
4041            ctx.file,
4042            ctx.source,
4043            &ctx.spec.target,
4044            false,
4045            Some(&ctx.lexical_scope_cache),
4046        );
4047        match resolution {
4048            LexicalTypeResolution::Resolved {
4049                unit, candidates, ..
4050            } if (!ctx
4051                .analyzer
4052                .type_alias_provider()
4053                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4054                || ctx.visibility.external_type_candidate_visible_in_context(
4055                    &ctx.analyzer,
4056                    ctx.file,
4057                    &ctx.spec.target,
4058                    node,
4059                ))
4060                && (same_visible_symbol(&unit, &ctx.spec.target)
4061                    || candidates
4062                        .iter()
4063                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
4064                && target_alias_candidates_visible(&candidates, node, ctx) =>
4065            {
4066                let matched =
4067                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
4068                if !template_type_component_preserves_target(matched, &candidates, ctx) {
4069                    continue;
4070                }
4071                if !matches.iter().any(|existing: &Node<'_>| {
4072                    existing.start_byte() == matched.start_byte()
4073                        && existing.end_byte() == matched.end_byte()
4074                }) {
4075                    matches.push(matched);
4076                }
4077            }
4078            // The ordinary lexical resolver can remain ambiguous when the
4079            // qualified terminal is an alias whose canonical target is not
4080            // indexed (for example, `Hash::Digest` aliases an external
4081            // `std::array`). The target-guided path below still requires one
4082            // physically visible logical class for every emitted prefix.
4083            LexicalTypeResolution::Ambiguous => {
4084                return (!inherited_injected_name_is_shadowed)
4085                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4086                    .flatten()
4087                    .map(|scope| vec![scope])
4088                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
4089            }
4090            LexicalTypeResolution::Resolved { .. } => {
4091                if let Some(matched) =
4092                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4093                {
4094                    matches.push(matched);
4095                }
4096                inherited_injected_name_is_shadowed |= component_count == 1;
4097            }
4098            LexicalTypeResolution::Missing => {
4099                if let Some(matched) =
4100                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4101                {
4102                    matches.push(matched);
4103                }
4104            }
4105        }
4106    }
4107    if matches.is_empty() {
4108        (!inherited_injected_name_is_shadowed)
4109            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4110            .flatten()
4111            .map(|scope| vec![scope])
4112            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
4113    } else {
4114        Some(matches)
4115    }
4116}
4117
4118/// Recover a class owner in a qualified expression when guard-aware lookup
4119/// cannot prove the owner. Keep the hit on the owner component, not the member.
4120enum TargetGuidedQualifiedValueOwnerHit<'tree> {
4121    Proven(Node<'tree>),
4122    Unproven(Node<'tree>),
4123}
4124
4125fn target_guided_qualified_value_owner_hit<'tree>(
4126    node: Node<'tree>,
4127    ctx: &ScanCtx<'_>,
4128) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
4129    let target = physically_visible_type_target(ctx)?;
4130    if !target.is_class() {
4131        return None;
4132    }
4133    let qualified = qualified_owner_components(node, ctx.source)?;
4134    let lexical_scope = match enclosing_lexical_scope_components(
4135        node,
4136        &ctx.analyzer,
4137        ctx.visibility,
4138        ctx.file,
4139        ctx.source,
4140    ) {
4141        LexicalScopeResolution::Resolved(scope) => scope,
4142        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4143            enclosing_namespace_components(node, ctx.source)
4144        }
4145    };
4146    let LexicalTypeResolution::Resolved {
4147        unit, candidates, ..
4148    } = ctx.visibility.resolve_type_components_lexically_for_target(
4149        &ctx.analyzer,
4150        ctx.file,
4151        &qualified.names,
4152        qualified.global,
4153        &lexical_scope,
4154        target,
4155    )
4156    else {
4157        return None;
4158    };
4159    let scope = qualified.nodes.last().copied()?;
4160    // Lexical component lookup does not consider source order, so it can
4161    // select a declaration that follows the reference. The forward selection
4162    // proves the owner only when that declaration is visible at the reference.
4163    let forward_uniquely_selects_target = match ctx
4164        .visibility
4165        .resolve_type_components_lexically_for_forward(
4166            &ctx.analyzer,
4167            ctx.file,
4168            &qualified.names,
4169            qualified.global,
4170            &lexical_scope,
4171        ) {
4172        LexicalTypeResolution::Resolved {
4173            unit, candidates, ..
4174        } if candidates.len() == 1 => {
4175            same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4176                && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4177                && ctx.visibility.external_type_candidate_visible_in_context(
4178                    &ctx.analyzer,
4179                    ctx.file,
4180                    &candidates[0],
4181                    scope,
4182                )
4183        }
4184        LexicalTypeResolution::Resolved { .. }
4185        | LexicalTypeResolution::Ambiguous
4186        | LexicalTypeResolution::Missing => false,
4187    };
4188    if forward_uniquely_selects_target
4189        || (same_visible_symbol(&unit, target)
4190            && !candidates.is_empty()
4191            && candidates
4192                .iter()
4193                .all(|candidate| same_visible_symbol(candidate, target)))
4194    {
4195        Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4196    } else if candidates
4197        .iter()
4198        .any(|candidate| same_visible_symbol(candidate, target))
4199    {
4200        Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4201    } else {
4202        None
4203    }
4204}
4205
4206/// Resolve a nested class-owned alias when the indexed alias path is not a
4207/// standalone type candidate. The C++ index stores `basic_json::type_error`
4208/// as a synthetic child of `basic_json`, while source can qualify it through
4209/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4210/// then canonicalize the structured member alias against the requested type.
4211fn target_guided_nested_alias_type_scope<'tree>(
4212    node: Node<'tree>,
4213    qualified: &QualifiedOwnerComponents<'tree>,
4214    component_count: usize,
4215    ctx: &ScanCtx<'_>,
4216) -> Option<Node<'tree>> {
4217    if component_count < 2 {
4218        return None;
4219    }
4220    let (owner_components, member_name) =
4221        qualified.names[..component_count].split_at(component_count - 1);
4222    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4223        node,
4224        owner_components,
4225        qualified.global,
4226        &ctx.analyzer,
4227        ctx.visibility,
4228        &ctx.ordinary_type_imports,
4229        ctx.file,
4230        ctx.source,
4231    ) else {
4232        return None;
4233    };
4234    let member_name = member_name.first()?;
4235    let alias_provider = ctx.analyzer.type_alias_provider()?;
4236    ctx.visibility
4237        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4238        .into_iter()
4239        .filter(|member| alias_provider.is_type_alias(member))
4240        .find(|member| {
4241            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4242                &ctx.analyzer,
4243                ctx.file,
4244                member,
4245                node,
4246            ) || ctx
4247                .visibility
4248                .external_type_candidate_guard_compatible_in_context(
4249                    &ctx.analyzer,
4250                    ctx.file,
4251                    member,
4252                    node,
4253                );
4254            if !member_visible {
4255                return false;
4256            }
4257            same_visible_symbol(member, &ctx.spec.target)
4258                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4259        })
4260        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4261}
4262
4263fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4264    if ctx.visibility.structured_class_alias_resolves_to_target(
4265        &ctx.analyzer,
4266        ctx.file,
4267        candidate,
4268        &ctx.spec.target,
4269    ) {
4270        return ctx.spec.target.clone();
4271    }
4272    let structured = ctx
4273        .visibility
4274        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4275    structured.unwrap_or_else(|| candidate.clone())
4276}
4277
4278/// Preserve the alias component of a qualified reference when the alias target
4279/// is a dependent nested type and forward lookup retains its primary template
4280/// as the bounded identity.
4281fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4282    ctx.visibility
4283        .external_type_candidate_guard_compatible_in_context(
4284            &ctx.analyzer,
4285            ctx.file,
4286            candidate,
4287            node,
4288        )
4289}
4290
4291fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4292    ctx.visibility.is_physically_visible(ctx.file, candidate)
4293        && class_alias_guard_compatible(candidate, node, ctx)
4294}
4295
4296fn class_alias_path_preserves_target(
4297    candidate: &CodeUnit,
4298    target: &CodeUnit,
4299    ctx: &ScanCtx<'_>,
4300) -> bool {
4301    ctx.visibility.structured_class_alias_path_preserves_target(
4302        &ctx.analyzer,
4303        ctx.file,
4304        candidate,
4305        target,
4306    )
4307}
4308
4309fn target_guided_dependent_alias_qualifier_scope<'tree>(
4310    node: Node<'tree>,
4311    ctx: &ScanCtx<'_>,
4312) -> Option<Node<'tree>> {
4313    if !matches!(
4314        node.kind(),
4315        "qualified_identifier" | "scoped_type_identifier"
4316    ) {
4317        return None;
4318    }
4319    let target = physically_visible_type_target(ctx)?;
4320    let alias_provider = ctx.analyzer.type_alias_provider()?;
4321    if !target.is_class() || alias_provider.is_type_alias(target) {
4322        return None;
4323    }
4324    let nodes = cpp_name_component_nodes(node)?;
4325    let names = nodes
4326        .iter()
4327        .map(|component| node_text(*component, ctx.source).to_string())
4328        .collect::<Vec<_>>();
4329    let global = is_globally_qualified_cpp_name(node);
4330    let lexical_scope = match enclosing_lexical_scope_components(
4331        node,
4332        &ctx.analyzer,
4333        ctx.visibility,
4334        ctx.file,
4335        ctx.source,
4336    ) {
4337        LexicalScopeResolution::Resolved(scope) => scope,
4338        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4339            enclosing_namespace_components(node, ctx.source)
4340        }
4341    };
4342    for component_count in 1..=names.len() {
4343        let components = &names[..component_count];
4344        let name = components.last()?;
4345        let candidates = ctx
4346            .visibility
4347            .visible_identifier_candidates(ctx.file, name)
4348            .filter(|candidate| alias_provider.is_type_alias(candidate))
4349            .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4350            .filter(|candidate| {
4351                let candidate_components = canonical_cpp_scope_components(candidate);
4352                lexical_component_tiers(components, global, &lexical_scope)
4353                    .any(|tier| tier == candidate_components)
4354                    || (component_count == 1
4355                        && member_alias_owner_matches_reference_for(
4356                            candidate,
4357                            nodes[component_count - 1],
4358                            ctx,
4359                        ))
4360            })
4361            .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4362        let mut aliases: Vec<&CodeUnit> = Vec::new();
4363        for candidate in candidates {
4364            if !aliases
4365                .iter()
4366                .any(|existing| same_logical_symbol(existing, candidate))
4367            {
4368                aliases.push(candidate);
4369            }
4370        }
4371        if aliases.len() == 1 {
4372            return nodes.get(component_count - 1).copied();
4373        }
4374        if aliases.len() > 1 {
4375            return None;
4376        }
4377    }
4378    None
4379}
4380
4381/// Preserve an unqualified class-owned alias when its dependent target path
4382/// retains the requested primary template as the bounded forward identity.
4383fn target_guided_dependent_class_alias_leaf<'tree>(
4384    node: Node<'tree>,
4385    ctx: &ScanCtx<'_>,
4386) -> Option<Node<'tree>> {
4387    if node.kind() != "type_identifier"
4388        || is_declaration_name(node)
4389        || local_type_name_shadows(node, ctx)
4390    {
4391        return None;
4392    }
4393    let target = physically_visible_type_target(ctx)?;
4394    let alias_provider = ctx.analyzer.type_alias_provider()?;
4395    if !target.is_class() || alias_provider.is_type_alias(target) {
4396        return None;
4397    }
4398    let name = node_text(node, ctx.source);
4399    let aliases = ctx
4400        .visibility
4401        .visible_identifier_candidates(ctx.file, name)
4402        .filter(|candidate| alias_provider.is_type_alias(candidate))
4403        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4404        .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4405        .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4406        .collect::<Vec<_>>();
4407    matches!(aliases.as_slice(), [_]).then_some(node)
4408}
4409
4410/// Recover a namespace alias whose guard state blocks ordinary visibility.
4411/// Require one visible canonical target and an exact structured alias path.
4412fn target_guided_unproven_alias_type_reference<'tree>(
4413    node: Node<'tree>,
4414    candidates: &[CodeUnit],
4415    ctx: &ScanCtx<'_>,
4416) -> Option<Node<'tree>> {
4417    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4418    let target = physically_visible_type_target(ctx)?;
4419    if !target.is_class() {
4420        return None;
4421    }
4422    let alias_provider = ctx.analyzer.type_alias_provider()?;
4423    let (components, _) = type_reference_components(node, ctx.source)?;
4424    let hit = template_arguments
4425        .as_ref()
4426        .and_then(|_| template_reference_name_node(node))
4427        .map(function_terminal_node)
4428        .unwrap_or_else(|| function_terminal_node(node));
4429    candidates
4430        .iter()
4431        .filter(|candidate| {
4432            alias_provider.is_type_alias(candidate)
4433                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4434                && canonical_cpp_scope_components(candidate) == components
4435        })
4436        .find(|candidate| {
4437            template_arguments.as_ref().map_or_else(
4438                || {
4439                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4440                        || ctx.visibility.structured_alias_primary_preserves_target(
4441                            &ctx.analyzer,
4442                            ctx.file,
4443                            candidate,
4444                            target,
4445                        )
4446                },
4447                |arguments| {
4448                    ctx.visibility.template_alias_arguments_preserve_target(
4449                        &ctx.analyzer,
4450                        ctx.file,
4451                        candidate,
4452                        arguments,
4453                        target,
4454                    )
4455                },
4456            )
4457        })
4458        .map(|_| hit)
4459}
4460
4461fn target_alias_candidates_visible(
4462    candidates: &[CodeUnit],
4463    reference: Node<'_>,
4464    ctx: &ScanCtx<'_>,
4465) -> bool {
4466    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4467        return true;
4468    };
4469    if candidates.iter().any(|candidate| {
4470        !alias_provider.is_type_alias(candidate)
4471            && ctx.visibility.same_template_member_identity(
4472                &ctx.analyzer,
4473                candidate,
4474                &ctx.spec.target,
4475            )
4476    }) {
4477        return true;
4478    }
4479    let target_aliases = candidates
4480        .iter()
4481        .filter(|candidate| {
4482            alias_provider.is_type_alias(candidate)
4483                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4484        })
4485        .collect::<Vec<_>>();
4486    target_aliases.is_empty()
4487        || target_aliases
4488            .iter()
4489            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4490}
4491
4492fn type_candidate_visible_at_reference(
4493    candidate: &CodeUnit,
4494    reference: Node<'_>,
4495    ctx: &ScanCtx<'_>,
4496) -> bool {
4497    let class_owned_alias = ctx
4498        .analyzer
4499        .type_alias_provider()
4500        .is_some_and(|provider| provider.is_type_alias(candidate))
4501        && ctx
4502            .analyzer
4503            .parent_of(candidate)
4504            .is_some_and(|owner| owner.is_class());
4505    if class_owned_alias {
4506        let conditional_family = ctx
4507            .visibility
4508            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4509        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4510            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4511            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4512        let guard_match = ctx
4513            .visibility
4514            .external_type_candidate_guard_compatible_in_context(
4515                &ctx.analyzer,
4516                ctx.file,
4517                candidate,
4518                reference,
4519            );
4520        let general_match = conditional_family
4521            && ctx.visibility.external_type_candidate_visible_in_context(
4522                &ctx.analyzer,
4523                ctx.file,
4524                candidate,
4525                reference,
4526            );
4527        return owner_match && (guard_match || general_match);
4528    }
4529    ctx.visibility.external_type_candidate_visible_in_context(
4530        &ctx.analyzer,
4531        ctx.file,
4532        candidate,
4533        reference,
4534    )
4535}
4536
4537fn unqualified_reference_selects_inherited_alias(
4538    candidate: &CodeUnit,
4539    reference: Node<'_>,
4540    ctx: &ScanCtx<'_>,
4541) -> bool {
4542    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4543        return false;
4544    };
4545    if global || components.len() != 1 {
4546        return false;
4547    }
4548    matches!(
4549        resolve_type_node_lexically_for_target(
4550            reference,
4551            &ctx.analyzer,
4552            ctx.visibility,
4553            &ctx.ordinary_type_imports,
4554            ctx.file,
4555            ctx.source,
4556            candidate,
4557            Some(&ctx.lexical_scope_cache),
4558            ctx.recovered_sentinel_scope(reference).as_deref(),
4559        ),
4560        LexicalTypeResolution::Resolved {
4561            ref unit,
4562            ref candidates,
4563            ..
4564        } if ctx
4565            .visibility
4566            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4567            || candidates.iter().any(|resolved| {
4568                ctx.visibility.same_template_member_identity(
4569                    &ctx.analyzer,
4570                    resolved,
4571                    candidate,
4572                )
4573            })
4574    )
4575}
4576
4577fn qualified_reference_selects_type_candidate(
4578    candidate: &CodeUnit,
4579    reference: Node<'_>,
4580    ctx: &ScanCtx<'_>,
4581) -> bool {
4582    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4583        return false;
4584    };
4585    if components.len() < 2 {
4586        return false;
4587    }
4588    let candidate_components = canonical_cpp_scope_components(candidate);
4589    match select_qualifier_owner(
4590        reference,
4591        global,
4592        &ctx.analyzer,
4593        ctx.visibility,
4594        &ctx.ordinary_type_imports,
4595        ctx.file,
4596        ctx.source,
4597    ) {
4598        QualifierOwnerSelection::Unchanged => {}
4599        QualifierOwnerSelection::Selected(selected) => {
4600            return selected == candidate_components;
4601        }
4602        QualifierOwnerSelection::Ambiguous => return false,
4603    }
4604    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4605        match enclosing_lexical_scope_components(
4606            reference,
4607            &ctx.analyzer,
4608            ctx.visibility,
4609            ctx.file,
4610            ctx.source,
4611        ) {
4612            LexicalScopeResolution::Resolved(scope) => scope,
4613            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4614                enclosing_namespace_components(reference, ctx.source)
4615            }
4616        }
4617    });
4618    lexical_component_tiers(&components, global, &lexical_scope)
4619        .any(|qualified| qualified == candidate_components)
4620}
4621
4622fn qualified_type_component_hit_node<'tree>(
4623    component: Node<'tree>,
4624    qualified: Node<'tree>,
4625) -> Node<'tree> {
4626    let mut current = component;
4627    while let Some(parent) = current.parent() {
4628        let is_type_name = matches!(
4629            parent.kind(),
4630            "template_type"
4631                | "qualified_identifier"
4632                | "scoped_identifier"
4633                | "scoped_type_identifier"
4634        ) && parent
4635            .child_by_field_name("name")
4636            .is_some_and(|name| same_node(name, current));
4637        if !is_type_name {
4638            break;
4639        }
4640        current = parent;
4641        if same_node(parent, qualified) {
4642            break;
4643        }
4644    }
4645    current
4646}
4647
4648fn template_type_component_preserves_target(
4649    node: Node<'_>,
4650    candidates: &[CodeUnit],
4651    ctx: &ScanCtx<'_>,
4652) -> bool {
4653    template_reference_candidates_select_target(
4654        node,
4655        candidates,
4656        &ctx.analyzer,
4657        ctx.visibility,
4658        ctx.file,
4659        ctx.source,
4660        &ctx.spec.target,
4661    )
4662}
4663
4664fn template_reference_candidates_select_target(
4665    node: Node<'_>,
4666    candidates: &[CodeUnit],
4667    analyzer: &CppGraphSource<'_>,
4668    visibility: &VisibilityIndex<'_>,
4669    file: &ProjectFile,
4670    source: &str,
4671    target: &CodeUnit,
4672) -> bool {
4673    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4674        return !visibility.is_template_specialization(target);
4675    };
4676    let direct_template_name =
4677        template_reference_name_node(node).map(|name| node_text(name, source));
4678    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4679        analyzer.type_alias_provider().is_some_and(|provider| {
4680            visibility
4681                .visible_identifier_candidates(file, name)
4682                .filter(|candidate| provider.is_type_alias(candidate))
4683                .any(|candidate| {
4684                    visibility.template_alias_arguments_preserve_target(
4685                        analyzer, file, candidate, &arguments, target,
4686                    )
4687                })
4688        })
4689    });
4690    named_alias_selects_target
4691        || candidates.iter().any(|candidate| {
4692            (same_visible_symbol(candidate, target)
4693                && visibility.is_primary_template(target)
4694                && direct_template_name == Some(candidate.identifier()))
4695                || visibility.template_alias_arguments_preserve_target(
4696                    analyzer, file, candidate, &arguments, target,
4697                )
4698                || visibility
4699                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4700                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4701        })
4702}
4703
4704fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4705    let template = if node.kind() == "template_type" {
4706        node
4707    } else {
4708        node.child_by_field_name("name")
4709            .filter(|name| name.kind() == "template_type")?
4710    };
4711    template.child_by_field_name("name")
4712}
4713
4714fn type_resolution_matches_target(
4715    node: Node<'_>,
4716    unit: &CodeUnit,
4717    candidates: &[CodeUnit],
4718    ctx: &ScanCtx<'_>,
4719) -> bool {
4720    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4721}
4722
4723fn type_resolution_matches_unit_target(
4724    node: Node<'_>,
4725    unit: &CodeUnit,
4726    candidates: &[CodeUnit],
4727    target: &CodeUnit,
4728    ctx: &ScanCtx<'_>,
4729) -> bool {
4730    target_alias_candidates_visible(candidates, node, ctx)
4731        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4732}
4733
4734/// The identity half of the type-resolution match, without the alias
4735/// visibility gate.
4736///
4737/// Use it only on the without-visibility fallback path, which reports an
4738/// unproven hit. An alias spelling does not contain the target identifier, so
4739/// the name-mention fallback can never recover a rejected alias reference: the
4740/// site would disappear instead of degrading to a reviewable hit.
4741fn type_resolution_identifies_unit_target(
4742    node: Node<'_>,
4743    unit: &CodeUnit,
4744    candidates: &[CodeUnit],
4745    target: &CodeUnit,
4746    ctx: &ScanCtx<'_>,
4747) -> bool {
4748    if !template_alias_owner_matches_reference(node, target, ctx) {
4749        return false;
4750    }
4751    if ctx.visibility.is_template_specialization(target)
4752        && cpp_template_reference_arguments(node, ctx.source).is_some()
4753    {
4754        let selected_unit =
4755            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4756                ctx.visibility
4757                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4758                    .ok()
4759            });
4760        return selected_unit
4761            .as_ref()
4762            .is_some_and(|selected| same_visible_symbol(selected, target))
4763            || template_reference_candidates_select_target(
4764                node,
4765                candidates,
4766                &ctx.analyzer,
4767                ctx.visibility,
4768                ctx.file,
4769                ctx.source,
4770                target,
4771            );
4772    }
4773    unit == target
4774        || ctx
4775            .visibility
4776            .same_template_member_identity(&ctx.analyzer, unit, target)
4777        || ctx.visibility.c_tag_declaration_family_matches_target(
4778            &ctx.analyzer,
4779            ctx.file,
4780            &candidates.iter().collect::<Vec<_>>(),
4781            target,
4782        )
4783        || ctx.visibility.structured_class_alias_resolves_to_target(
4784            &ctx.analyzer,
4785            ctx.file,
4786            unit,
4787            target,
4788        )
4789        || candidates.iter().any(|candidate| {
4790            ctx.visibility
4791                .same_template_member_identity(&ctx.analyzer, candidate, target)
4792                || ctx.visibility.structured_class_alias_resolves_to_target(
4793                    &ctx.analyzer,
4794                    ctx.file,
4795                    candidate,
4796                    target,
4797                )
4798        })
4799}
4800
4801/// Keep a member alias attached to the class specialization that declares it.
4802/// A target-guided lexical lookup can otherwise retain the primary alias when
4803/// the source reference is inside a partial specialization with the same
4804/// unqualified alias name. Compare the indexed template identities instead of
4805/// rendered text or suffixes.
4806fn template_alias_owner_matches_reference(
4807    node: Node<'_>,
4808    target: &CodeUnit,
4809    ctx: &ScanCtx<'_>,
4810) -> bool {
4811    if !ctx
4812        .analyzer
4813        .type_alias_provider()
4814        .is_some_and(|provider| provider.is_type_alias(target))
4815    {
4816        return true;
4817    }
4818    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4819        return true;
4820    };
4821    if !target_owner.is_class() {
4822        return true;
4823    }
4824    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4825        return true;
4826    };
4827    if !ctx.visibility.is_template_specialization(&target_owner)
4828        && !ctx.visibility.is_template_specialization(&reference_owner)
4829    {
4830        return true;
4831    }
4832    same_visible_symbol(&target_owner, &reference_owner)
4833}
4834
4835fn inherited_injected_class_qualifier_scope<'tree>(
4836    node: Node<'tree>,
4837    ctx: &ScanCtx<'_>,
4838) -> Option<Node<'tree>> {
4839    let qualified = qualified_owner_components(node, ctx.source)?;
4840    if qualified.global || qualified.names.is_empty() {
4841        return None;
4842    }
4843    let injected_name = &qualified.names[0];
4844    if !ctx.spec.target.is_class()
4845        || ctx.spec.target.identifier() != injected_name
4846        || physically_visible_type_target(ctx).is_none()
4847    {
4848        return None;
4849    }
4850    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4851    let owner = ctx.visibility.inherited_injected_class_owner(
4852        &ctx.analyzer,
4853        ctx.file,
4854        &enclosing_owner,
4855        injected_name,
4856    )?;
4857    same_visible_symbol(&owner, &ctx.spec.target)
4858        .then(|| qualified.nodes.first().copied())
4859        .flatten()
4860}
4861
4862/// Resolve each qualified type component against the inverse target while
4863/// preserving C++ lexical-tier precedence and structured alias identity.
4864fn target_guided_qualifier_type_scopes<'tree>(
4865    node: Node<'tree>,
4866    ctx: &ScanCtx<'_>,
4867) -> Option<Vec<Node<'tree>>> {
4868    if !matches!(
4869        node.kind(),
4870        "qualified_identifier" | "scoped_type_identifier"
4871    ) {
4872        return None;
4873    }
4874    let target = physically_visible_type_target(ctx)?;
4875    let qualified = qualified_owner_components(node, ctx.source)?;
4876    if !qualified.global
4877        && qualified
4878            .names
4879            .first()
4880            .is_some_and(|name| local_receiver_alias_type_node(node, name, ctx).is_some())
4881    {
4882        return static_qualifier_type_scopes_for_components(node, qualified, ctx);
4883    }
4884    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4885    // scope before falling back to suffix recovery.  A short unqualified
4886    // owner can have a same-spelled class in a nested namespace (for example
4887    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4888    // both as possible owners and then fails closed, even though the
4889    // translation unit's lexical scope selects the global class.  Keep the
4890    // suffix path for malformed namespace sentinels, where the parser does
4891    // not expose every indexed scope component.
4892    let lexical_scope = match enclosing_lexical_scope_components(
4893        node,
4894        &ctx.analyzer,
4895        ctx.visibility,
4896        ctx.file,
4897        ctx.source,
4898    ) {
4899        LexicalScopeResolution::Resolved(scope) => scope,
4900        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4901            enclosing_namespace_components(node, ctx.source)
4902        }
4903    };
4904    let indexed_owner_scope =
4905        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4906    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4907    let mut matches = Vec::new();
4908    for component_count in 1..=qualified.names.len() {
4909        let components = &qualified.names[..component_count];
4910        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4911            .collect::<Vec<_>>();
4912        let name = components.last()?;
4913        let mut candidates = Vec::new();
4914        let mut exact_candidates = Vec::new();
4915        for candidate in ctx
4916            .visibility
4917            .visible_identifier_candidates(ctx.file, name)
4918            .filter(|candidate| candidate.is_class())
4919            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4920        {
4921            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4922                brokk_bifrost_core::analyzer::Language::Cpp,
4923                &cpp_name_for(candidate),
4924            );
4925            if !candidate_components.ends_with(components)
4926                || candidates
4927                    .iter()
4928                    .any(|existing| same_logical_symbol(existing, candidate))
4929            {
4930                continue;
4931            }
4932            let exact_lexical_scope = lexical_tiers
4933                .iter()
4934                .any(|expected| expected == &candidate_components);
4935            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4936            let structured_owner_match = indexed_owner_scope
4937                .as_ref()
4938                .is_some_and(|owner| owner.starts_with(candidate_owner))
4939                || recovered_owner_scope
4940                    .as_ref()
4941                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4942            let class_alias_owner_match = ctx
4943                .analyzer
4944                .type_alias_provider()
4945                .is_some_and(|provider| provider.is_type_alias(candidate))
4946                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4947            let macro_namespace_owner_match = is_declaration_name(node)
4948                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4949            if qualified.global && !exact_lexical_scope {
4950                continue;
4951            }
4952            if components.len() == 1
4953                && candidate_components != components
4954                && !exact_lexical_scope
4955                && !structured_owner_match
4956                && !class_alias_owner_match
4957                && !macro_namespace_owner_match
4958            {
4959                continue;
4960            }
4961            candidates.push(candidate.clone());
4962            if exact_lexical_scope {
4963                exact_candidates.push(candidate.clone());
4964            }
4965        }
4966        if !exact_candidates.is_empty() {
4967            candidates = exact_candidates;
4968        }
4969        // A typedef spelling can qualify nested C++ members while forward
4970        // lookup canonicalizes that spelling to its underlying class. Preserve
4971        // the exact alias prefix only when structured alias resolution proves
4972        // that it denotes this inverse target.
4973        let canonical_alias_target_matches = matches!(
4974            candidates.as_slice(),
4975            [candidate]
4976                if ctx
4977                    .analyzer
4978                    .type_alias_provider()
4979                    .is_some_and(|provider| provider.is_type_alias(candidate))
4980                    && type_candidate_visible_at_reference(candidate, node, ctx)
4981                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4982                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4983                        brokk_bifrost_core::analyzer::Language::Cpp,
4984                        &cpp_name_for(candidate),
4985                    ) == components
4986                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
4987        );
4988        let direct_alias_target = ctx
4989            .analyzer
4990            .type_alias_provider()
4991            .is_some_and(|provider| provider.is_type_alias(target))
4992            && candidates
4993                .iter()
4994                .any(|candidate| same_symbol(candidate, target));
4995        let unique_target = matches!(
4996            candidates.as_slice(),
4997            [candidate] if same_visible_symbol(candidate, target)
4998        );
4999        if direct_alias_target || unique_target || canonical_alias_target_matches {
5000            let matched = if ctx
5001                .analyzer
5002                .type_alias_provider()
5003                .is_some_and(|provider| provider.is_type_alias(target))
5004                && ctx
5005                    .analyzer
5006                    .parent_of(target)
5007                    .is_some_and(|owner| owner.is_class())
5008                && ctx
5009                    .visibility
5010                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
5011                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
5012            {
5013                // The alias declaration owns the terminal component. Keep
5014                // the inverse range narrow so `MathLib::bigint` records the
5015                // `bigint` token, not the complete qualified owner path.
5016                qualified.nodes[component_count - 1]
5017            } else {
5018                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
5019            };
5020            if template_type_component_preserves_target(matched, &candidates, ctx) {
5021                matches.push(matched);
5022            }
5023        }
5024    }
5025    (!matches.is_empty()).then_some(matches)
5026}
5027
5028fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
5029    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5030        return false;
5031    };
5032    ctx.visibility
5033        .visible_identifier_candidates(ctx.file, target.identifier())
5034        .any(|candidate| {
5035            alias_provider.is_type_alias(candidate)
5036                && candidate.identifier() == target.identifier()
5037                && !same_visible_symbol(candidate, target)
5038                && ctx
5039                    .analyzer
5040                    .parent_of(candidate)
5041                    .is_some_and(|owner| owner.is_class())
5042        })
5043}
5044
5045/// Recover an out-of-line owner when the owner declaration and the reference
5046/// use different unknown preprocessor guards. Keep this result unproven.
5047fn target_guided_unproven_out_of_line_owner<'tree>(
5048    node: Node<'tree>,
5049    ctx: &ScanCtx<'_>,
5050) -> Option<Node<'tree>> {
5051    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
5052        || !is_declaration_name(node)
5053    {
5054        return None;
5055    }
5056    let target = physically_visible_type_target(ctx)?;
5057    if !target.is_class() {
5058        return None;
5059    }
5060    let qualified = qualified_owner_components(node, ctx.source)?;
5061    let target_components = canonical_cpp_scope_components(target);
5062    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
5063    let parser_scope = enclosing_namespace_components(node, ctx.source);
5064    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
5065        if !parser_scope.is_empty() || target_namespace.is_empty() {
5066            Some(parser_scope)
5067        } else {
5068            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5069        }
5070    })?;
5071    if has_malformed_wrapper_function_definition_ancestor(node)
5072        && target_namespace.starts_with(&scope)
5073        && target_namespace.len() > scope.len()
5074    {
5075        scope = target_namespace.to_vec();
5076    }
5077    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
5078        .any(|components| components == target_components)
5079    {
5080        return None;
5081    }
5082    let owner_name = qualified.names.last()?;
5083    let candidates = ctx
5084        .visibility
5085        .visible_identifier_candidates(ctx.file, owner_name)
5086        .filter(|candidate| {
5087            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
5088        })
5089        .collect::<Vec<_>>();
5090    if candidates.is_empty()
5091        || candidates
5092            .iter()
5093            .any(|candidate| !same_visible_symbol(candidate, target))
5094    {
5095        return None;
5096    }
5097    qualified.nodes.last().copied()
5098}
5099
5100fn macro_namespace_scope_matches(
5101    candidate_owner: &[String],
5102    node: Node<'_>,
5103    ctx: &ScanCtx<'_>,
5104) -> bool {
5105    let namespace = enclosing_namespace_components(node, ctx.source);
5106    if namespace.is_empty() || candidate_owner.is_empty() {
5107        return false;
5108    }
5109    let mut expanded_owner = Vec::new();
5110    for component in candidate_owner {
5111        if let Some(replacement) =
5112            ctx.visibility
5113                .object_macro_replacement_at(ctx.file, component, node.start_byte())
5114        {
5115            let replacement_components =
5116                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5117                    brokk_bifrost_core::analyzer::Language::Cpp,
5118                    &replacement,
5119                );
5120            if replacement_components.is_empty() {
5121                return false;
5122            }
5123            expanded_owner.extend(replacement_components);
5124        } else {
5125            expanded_owner.push(component.clone());
5126        }
5127    }
5128    expanded_owner == namespace
5129}
5130
5131fn target_guided_missing_type_leaf<'tree>(
5132    node: Node<'tree>,
5133    ctx: &ScanCtx<'_>,
5134) -> Option<Node<'tree>> {
5135    physically_visible_type_target(ctx)?;
5136    target_guided_missing_dependent_nested_type_leaf(node, ctx)
5137        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
5138        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
5139        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
5140        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
5141        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
5142}
5143
5144/// Recover a bare class-owned alias whose structured canonical target is the
5145/// requested type. The class owner must enclose the reference, and every alias
5146/// with that spelling in the owner chain must preserve the same target.
5147fn target_guided_missing_class_alias_target_type_leaf<'tree>(
5148    node: Node<'tree>,
5149    ctx: &ScanCtx<'_>,
5150) -> Option<Node<'tree>> {
5151    if node.kind() != "type_identifier"
5152        || is_declaration_name(node)
5153        || local_type_name_shadows(node, ctx)
5154    {
5155        return None;
5156    }
5157    let alias_provider = ctx.analyzer.type_alias_provider()?;
5158    let name = node_text(node, ctx.source);
5159    let aliases = ctx
5160        .visibility
5161        .visible_identifier_candidates(ctx.file, name)
5162        .filter(|candidate| alias_provider.is_type_alias(candidate))
5163        .filter(|candidate| {
5164            ctx.analyzer
5165                .parent_of(candidate)
5166                .is_some_and(|owner| owner.is_class())
5167        })
5168        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5169        .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5170        .collect::<Vec<_>>();
5171    (!aliases.is_empty()
5172        && aliases.iter().all(|candidate| {
5173            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5174        }))
5175    .then_some(node)
5176}
5177
5178/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5179/// type only when the indexed class owner proves the alias declaration. This
5180/// narrow path covers malformed class bodies without accepting unrelated aliases.
5181fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5182    node: Node<'tree>,
5183    ctx: &ScanCtx<'_>,
5184) -> Option<Node<'tree>> {
5185    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5186        matches!(
5187            declaration.kind(),
5188            "parameter_declaration" | "optional_parameter_declaration"
5189        )
5190    });
5191    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5192        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5193    });
5194    if !parameter && !placement_new_type {
5195        return None;
5196    }
5197    if !ctx
5198        .analyzer
5199        .type_alias_provider()
5200        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5201        || !type_alias_owner_matches_structured_reference(node, ctx)
5202    {
5203        return None;
5204    }
5205    target_guided_missing_declaration_type_leaf(node, ctx)
5206}
5207
5208/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5209/// sentinel prevents ordinary lexical resolution. The indexed target must have
5210/// an indexed class parent, the structured owner path must compose with one
5211/// proven lexical namespace source, and every visible candidate at that exact
5212/// owner path must be the indexed parent. This keeps the fallback owner-based;
5213/// a same-spelled nested type under another template remains unproven.
5214fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5215    node: Node<'tree>,
5216    ctx: &ScanCtx<'_>,
5217) -> Option<Node<'tree>> {
5218    if !matches!(
5219        node.kind(),
5220        "qualified_identifier" | "scoped_type_identifier"
5221    ) || !qualified_type_scope_contains_template(node)
5222    {
5223        return None;
5224    }
5225    let name = node
5226        .child_by_field_name("name")
5227        .filter(|name| name.kind() == "type_identifier")?;
5228    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5229        return None;
5230    }
5231    // Template arguments that select a specialization, or that cannot be
5232    // bound, decide the owner; the owner-spelling fallback below reads the
5233    // qualifier without them and would name the primary template (#6902).
5234    if !matches!(
5235        select_qualifier_owner(
5236            node,
5237            is_globally_qualified_cpp_name(node),
5238            &ctx.analyzer,
5239            ctx.visibility,
5240            &ctx.ordinary_type_imports,
5241            ctx.file,
5242            ctx.source,
5243        ),
5244        QualifierOwnerSelection::Unchanged
5245    ) {
5246        return None;
5247    }
5248    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5249    if !owner_target.is_class() {
5250        return None;
5251    }
5252    let owner = node.child_by_field_name("scope")?;
5253    let owner_resolution = resolve_type_node_lexically_for_target(
5254        owner,
5255        &ctx.analyzer,
5256        ctx.visibility,
5257        &ctx.ordinary_type_imports,
5258        ctx.file,
5259        ctx.source,
5260        &owner_target,
5261        Some(&ctx.lexical_scope_cache),
5262        ctx.recovered_sentinel_scope(owner).as_deref(),
5263    );
5264    if matches!(
5265        owner_resolution,
5266        LexicalTypeResolution::Resolved {
5267            ref unit,
5268            ref candidates,
5269            ..
5270        } if type_resolution_matches_unit_target(
5271            owner,
5272            unit,
5273            candidates,
5274            &owner_target,
5275            ctx,
5276        )
5277    ) {
5278        return Some(name);
5279    }
5280
5281    let qualified = qualified_owner_components(node, ctx.source)?;
5282    let namespace = ctx
5283        .lexical_scope_cache
5284        .orphaned
5285        .enclosing_namespace_components(node, ctx.source);
5286    let indexed_scope = ctx
5287        .recovered_sentinel_scope(node)
5288        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5289        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5290    let owner_components = canonical_cpp_scope_components(&owner_target);
5291    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5292        .any(|components| components == owner_components)
5293    {
5294        return None;
5295    }
5296    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5297        .into_iter()
5298        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5299        .collect::<Vec<_>>();
5300    (!scoped_candidates.is_empty()
5301        && scoped_candidates
5302            .iter()
5303            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5304    .then_some(name)
5305}
5306
5307/// Recover a nested type-alias reference when parser recovery leaves an
5308/// unqualified template argument under a member function.  The ordinary
5309/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5310/// even though the indexed callable owner proves that the reference is inside
5311/// the class which declares the target alias.
5312fn target_guided_missing_member_alias_type_leaf<'tree>(
5313    node: Node<'tree>,
5314    ctx: &ScanCtx<'_>,
5315) -> Option<Node<'tree>> {
5316    if !is_cpp_template_argument_type_leaf(node)
5317        || is_declaration_name(node)
5318        || ctx
5319            .target_declaration_ranges
5320            .iter()
5321            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5322        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5323        || local_type_name_shadows(node, ctx)
5324        || !ctx
5325            .analyzer
5326            .type_alias_provider()
5327            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5328    {
5329        return None;
5330    }
5331    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5332    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5333    if !owner_scope_matches
5334        && !indexed_scope.is_some_and(|scope| {
5335            indexed_scope_matches_target_name(
5336                &scope,
5337                &[ctx.spec.target.identifier().to_string()],
5338                false,
5339                &ctx.spec.target,
5340            )
5341        })
5342    {
5343        return None;
5344    }
5345    // The indexed symbol table intentionally retains declarations from every
5346    // preprocessor branch and from later source positions.  The recovered
5347    // class-owner scope proves the spelling, but it does not prove that this
5348    // alias was active and introduced before the reference.  Apply the same
5349    // structured guard/source-order check used by the ordinary resolver before
5350    // turning the target-guided recovery into a proven hit.
5351    if !(ctx.visibility.external_type_candidate_visible_in_context(
5352        &ctx.analyzer,
5353        ctx.file,
5354        &ctx.spec.target,
5355        node,
5356    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5357    {
5358        return None;
5359    }
5360    let target_visible = ctx
5361        .visibility
5362        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5363        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5364    target_visible.then_some(node)
5365}
5366
5367/// Recover a class/enum template argument only when the parser's ordinary
5368/// lexical lookup failed but the indexed scope and visible declaration still
5369/// prove the exact target. This intentionally excludes aliases: an alias
5370/// argument needs its own template-argument selection path, while a direct
5371/// class/enum argument can be identified by its canonical scope and symbol.
5372fn target_guided_missing_template_argument_type_leaf<'tree>(
5373    node: Node<'tree>,
5374    ctx: &ScanCtx<'_>,
5375) -> Option<Node<'tree>> {
5376    let target = &ctx.spec.target;
5377    let name = node_text(node, ctx.source);
5378    if !target.is_class()
5379        || !is_cpp_template_argument_type_leaf(node)
5380        || is_declaration_name(node)
5381        || name != target.identifier()
5382        || ctx.local_shadows.is_shadowed(name)
5383        || local_type_name_shadows(node, ctx)
5384        || !ctx.visibility.is_physically_visible(ctx.file, target)
5385        || ctx
5386            .analyzer
5387            .type_alias_provider()
5388            .is_some_and(|provider| provider.is_type_alias(target))
5389    {
5390        return None;
5391    }
5392
5393    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5394    let target_components = canonical_cpp_scope_components(target);
5395    if target_components.last().map(String::as_str) != Some(name)
5396        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5397            .any(|components| components == target_components)
5398    {
5399        return None;
5400    }
5401
5402    // The direct visible class candidate supplies the declaration identity;
5403    // the scope check above supplies its canonical owner path. Do not let an
5404    // alias or a same-scoped competing class enter this recovery path.
5405    let candidates = visible_type_identifier_candidates(ctx, name);
5406    if candidates.is_empty()
5407        || candidates.iter().any(|candidate| {
5408            !candidate.is_class()
5409                || ctx
5410                    .analyzer
5411                    .type_alias_provider()
5412                    .is_some_and(|provider| provider.is_type_alias(candidate))
5413                || (!same_visible_symbol(candidate, target)
5414                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5415                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5416        })
5417        || !candidates
5418            .iter()
5419            .any(|candidate| same_visible_symbol(candidate, target))
5420    {
5421        return None;
5422    }
5423
5424    // Physical visibility covers the file/import projection; this second
5425    // guard preserves declaration ordering and preprocessor branch identity.
5426    ctx.visibility
5427        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5428        .then_some(node)
5429}
5430
5431/// Recover the class owner of an out-of-line member whose trailing attribute
5432/// macro was parsed as a separate function definition around the real body.
5433fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5434    let mut function = node;
5435    while function.kind() != "function_definition" {
5436        function = ctx.ancestry.parent(function)?;
5437    }
5438    let macro_name = function_definition_name_node(function)?;
5439    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5440        return None;
5441    }
5442
5443    // An unknown trailing attribute macro can split one real definition into
5444    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5445    // macro-named function definition that owns the body. Recover only that
5446    // exact CST sequence; a complete declaration or a non-macro function is
5447    // an ordinary independent construct.
5448    let declaration = function.prev_named_sibling()?;
5449    if declaration.kind() != "declaration"
5450        || !declaration.has_error()
5451        || function.start_position().row > declaration.end_position().row + 1
5452    {
5453        return None;
5454    }
5455    let mut missing_semicolon = false;
5456    let mut real_semicolon = false;
5457    for child in children_iter(declaration) {
5458        if child.kind() == ";" {
5459            missing_semicolon |= child.is_missing();
5460            real_semicolon |= !child.is_missing();
5461        }
5462    }
5463    if !missing_semicolon || real_semicolon {
5464        return None;
5465    }
5466
5467    let initializer = declaration.child_by_field_name("declarator")?;
5468    if initializer.kind() != "init_declarator"
5469        || initializer
5470            .child_by_field_name("value")
5471            .is_none_or(|value| value.kind() != "argument_list")
5472    {
5473        return None;
5474    }
5475    let qualified_name = initializer
5476        .child_by_field_name("declarator")
5477        .and_then(declarator_name_node)?;
5478    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5479    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5480    match ctx.visibility.resolve_type_components_lexically(
5481        &ctx.analyzer,
5482        ctx.file,
5483        &qualified.names,
5484        qualified.global,
5485        &lexical_scope,
5486    ) {
5487        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5488        LexicalTypeResolution::Resolved { .. }
5489        | LexicalTypeResolution::Ambiguous
5490        | LexicalTypeResolution::Missing => None,
5491    }
5492}
5493
5494/// A class member alias is visible throughout its complete class scope, even
5495/// when its declaration byte follows a recovered out-of-line member's
5496/// trailing return type. Match the indexed owner path structurally before
5497/// allowing the guard-only visibility check above to waive source ordering.
5498fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5499    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5500}
5501
5502fn member_alias_owner_matches_reference_for(
5503    target: &CodeUnit,
5504    node: Node<'_>,
5505    ctx: &ScanCtx<'_>,
5506) -> bool {
5507    let Some(owner) = ctx.analyzer.parent_of(target) else {
5508        return false;
5509    };
5510    if !owner.is_class() {
5511        return false;
5512    }
5513    let reference_owner = ctx
5514        .class_ranges
5515        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5516        .or_else(|| structured_enclosing_owner(node, ctx));
5517    if reference_owner.as_ref().is_some_and(|reference_owner| {
5518        ctx.visibility
5519            .same_template_owner_identity(&owner, reference_owner)
5520    }) {
5521        return true;
5522    }
5523    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5524        ctx.visibility
5525            .same_template_owner_identity(&owner, &reference_owner)
5526    }) {
5527        return true;
5528    }
5529    if reference_owner.is_some_and(|reference_owner| {
5530        matches!(
5531            resolve_declaring_member_owner(
5532                &ctx.analyzer,
5533                ctx.visibility,
5534                ctx.file,
5535                &reference_owner,
5536                target.identifier(),
5537            ),
5538            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5539                if ctx
5540                    .visibility
5541                    .same_template_owner_identity(&owner, &declaring_owner)
5542        )
5543    }) {
5544        return true;
5545    }
5546    let range = Range {
5547        start_byte: node.start_byte(),
5548        end_byte: node.end_byte(),
5549        start_line: node.start_position().row + 1,
5550        end_line: node.end_position().row + 1,
5551    };
5552    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5553    while let Some(candidate) = indexed_enclosing {
5554        if candidate.is_class()
5555            && ctx
5556                .visibility
5557                .same_template_owner_identity(&owner, &candidate)
5558        {
5559            return true;
5560        }
5561        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5562    }
5563    if let Some(reference_body) = malformed_recovered_class_body(node) {
5564        let mut root = node;
5565        while let Some(parent) = ctx.ancestry.parent(root) {
5566            root = parent;
5567        }
5568        if ctx.analyzer.ranges(target).iter().any(|range| {
5569            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5570                .and_then(malformed_recovered_class_body)
5571                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5572        }) {
5573            return true;
5574        }
5575    }
5576    if structured_enclosing_owner(node, ctx)
5577        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5578    {
5579        return true;
5580    }
5581    let owner_components = canonical_cpp_scope_components(&owner);
5582    if ctx
5583        .recovered_sentinel_scope(node)
5584        .is_some_and(|scope| scope == owner_components)
5585    {
5586        return true;
5587    }
5588    if matches!(
5589        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5590            node,
5591            &ctx.analyzer,
5592            ctx.visibility,
5593            ctx.file,
5594            ctx.source,
5595            false,
5596            false,
5597            Some(&ctx.lexical_scope_cache),
5598        ),
5599        LexicalScopeResolution::Resolved(reference_scope)
5600            if reference_scope == owner_components
5601    ) {
5602        return true;
5603    }
5604    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5605    else {
5606        return false;
5607    };
5608    !owner_components.is_empty() && reference_scope == owner_components
5609}
5610
5611fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5612    loop {
5613        if node.kind() == "compound_statement"
5614            && node
5615                .parent()
5616                .is_some_and(|parent| parent.kind() == "declaration_list")
5617            && node.prev_named_sibling().is_some_and(|header| {
5618                header.kind() == "ERROR"
5619                    && header.end_byte() <= node.start_byte()
5620                    && error_contains_class_header(header)
5621            })
5622        {
5623            return Some(node);
5624        }
5625        node = node.parent()?;
5626    }
5627}
5628
5629/// Whether a shallow parser error contains a recognizable class header.
5630pub fn error_contains_class_header(node: Node<'_>) -> bool {
5631    let mut pending = vec![(node, 0usize)];
5632    while let Some((current, depth)) = pending.pop() {
5633        if matches!(current.kind(), "class" | "struct" | "union") {
5634            let mut sibling = current.next_sibling();
5635            let mut saw_name = false;
5636            while let Some(candidate) = sibling {
5637                match candidate.kind() {
5638                    "comment" => {}
5639                    "{" | "base_class_clause" | ":" => return saw_name,
5640                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5641                    _ if !candidate.is_named() => {}
5642                    _ => break,
5643                }
5644                sibling = candidate.next_sibling();
5645            }
5646        }
5647        if depth >= 1 {
5648            continue;
5649        }
5650        let mut cursor = current.walk();
5651        pending.extend(
5652            current
5653                .children(&mut cursor)
5654                .filter(|child| child.kind() != "compound_statement")
5655                .map(|child| (child, depth + 1)),
5656        );
5657    }
5658    false
5659}
5660
5661fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5662    has_ancestor_kind(node, "compound_statement")
5663        && ctx
5664            .visibility
5665            .external_type_candidate_guard_compatible_in_context(
5666                &ctx.analyzer,
5667                ctx.file,
5668                &ctx.spec.target,
5669                node,
5670            )
5671}
5672
5673fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5674    ctx.analyzer
5675        .type_alias_provider()
5676        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5677        && member_alias_owner_matches_reference(node, ctx)
5678}
5679
5680/// A nested class can use aliases declared by any enclosing class. Preserve
5681/// that structured owner chain for malformed macro-return nodes, whose phantom
5682/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5683fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5684    if !ctx
5685        .analyzer
5686        .type_alias_provider()
5687        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5688    {
5689        return false;
5690    }
5691    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5692        return false;
5693    };
5694    let mut reference_owner = structured_enclosing_owner(node, ctx);
5695    while let Some(owner) = reference_owner {
5696        if same_logical_symbol(&target_owner, &owner) {
5697            return true;
5698        }
5699        reference_owner = ctx.analyzer.parent_of(&owner);
5700    }
5701    false
5702}
5703
5704/// An enclosing class alias is only usable when no nearer class declares the
5705/// same type name. The recovered macro-return path does not have a complete
5706/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5707/// rule before the enclosing alias fast path runs.
5708fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5709    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5710        return false;
5711    };
5712    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5713        return false;
5714    };
5715    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5716        return false;
5717    };
5718    let candidates = ctx
5719        .visibility
5720        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5721        .filter(|candidate| {
5722            candidate.is_class()
5723                && alias_provider.is_type_alias(candidate)
5724                && !same_visible_symbol(candidate, &ctx.spec.target)
5725        })
5726        .cloned()
5727        .collect::<Vec<_>>();
5728
5729    let mut owner = Some(reference_owner);
5730    while let Some(owner_unit) = owner {
5731        if same_logical_symbol(&target_owner, &owner_unit) {
5732            return false;
5733        }
5734        if candidates.iter().any(|candidate| {
5735            ctx.analyzer
5736                .parent_of(candidate)
5737                .is_some_and(|candidate_owner| {
5738                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5739                })
5740                && ctx
5741                    .visibility
5742                    .external_type_candidate_guard_compatible_in_context(
5743                        &ctx.analyzer,
5744                        ctx.file,
5745                        candidate,
5746                        node,
5747                    )
5748        }) {
5749            return true;
5750        }
5751        owner = ctx.analyzer.parent_of(&owner_unit);
5752    }
5753    false
5754}
5755
5756/// Whether a type spelling can name a function-local class target at all.
5757///
5758/// Only block-scope lookup of an unqualified head reaches a local class, so
5759/// the spelling must not be `::`-rooted and its head must resolve to the
5760/// target or to a local class that encloses it. Anything else names some
5761/// other type, and neither a proven nor an unproven hit may claim it. Every
5762/// other target is unaffected.
5763fn local_class_target_reachable(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5764    if !ctx.spec.target.is_class() || !is_function_local_unit(&ctx.spec.target) {
5765        return true;
5766    }
5767    type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
5768        !global
5769            && matches!(
5770                function_local_type_resolution(
5771                    &ctx.analyzer,
5772                    ctx.file,
5773                    node,
5774                    &components[..1],
5775                    ctx.source,
5776                ),
5777                Some(LexicalTypeResolution::Resolved { unit, .. })
5778                    if ctx.spec.target.fq().starts_with(unit.fq())
5779            )
5780    })
5781}
5782
5783fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5784    if cpp_active_template_type_parameter(
5785        node,
5786        ctx.spec.target.identifier(),
5787        ctx.source,
5788        &ctx.ancestry,
5789    ) {
5790        return true;
5791    }
5792    // A function-local class target is itself one of the local declarations
5793    // the walk below treats as shadows. A spelling reaches a local class only
5794    // through block-scope lookup of its head: a `::`-rooted name never does,
5795    // and an unqualified name or a qualifier head is shadowed exactly when
5796    // that lookup names another declaration. A later component of a qualified
5797    // name is decided by ordinary resolution, which applies the same lookup
5798    // to its head.
5799    if ctx.spec.target.is_class() && is_function_local_unit(&ctx.spec.target) {
5800        let qualifier = ctx.ancestry.parent(node).filter(|parent| {
5801            matches!(
5802                parent.kind(),
5803                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5804            )
5805        });
5806        if let Some(qualifier) = qualifier {
5807            let scope = qualifier.child_by_field_name("scope");
5808            if scope.is_none() {
5809                return true;
5810            }
5811            let is_head = scope == Some(node)
5812                && ctx.ancestry.parent(qualifier).is_none_or(|outer| {
5813                    !matches!(
5814                        outer.kind(),
5815                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5816                    ) || outer.child_by_field_name("scope") == Some(qualifier)
5817                });
5818            if !is_head {
5819                return false;
5820            }
5821        }
5822        return !matches!(
5823            function_local_type_resolution(
5824                &ctx.analyzer,
5825                ctx.file,
5826                node,
5827                &[ctx.spec.target.identifier().to_string()],
5828                ctx.source,
5829            ),
5830            Some(LexicalTypeResolution::Resolved { unit, .. })
5831                if same_visible_symbol(&unit, &ctx.spec.target)
5832        );
5833    }
5834    let Some(callable) = nearest_callable_scope(node) else {
5835        return false;
5836    };
5837    let mut root_callable = callable;
5838    let mut ancestor = ctx.ancestry.parent(callable);
5839    while let Some(current) = ancestor {
5840        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5841            root_callable = current;
5842        }
5843        ancestor = ctx.ancestry.parent(current);
5844    }
5845
5846    let mut stack = vec![root_callable];
5847    while let Some(current) = stack.pop() {
5848        if current.start_byte() >= node.start_byte() {
5849            continue;
5850        }
5851        if let Some(name) = local_type_name_declaration_node(current)
5852            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5853            && nearest_callable_scope(current).is_some_and(|owner| {
5854                !is_malformed_wrapper_function_definition(owner)
5855                    && owner.start_byte() <= callable.start_byte()
5856                    && callable.end_byte() <= owner.end_byte()
5857            })
5858            && local_alias_scope_contains_node(current, node)
5859        {
5860            return true;
5861        }
5862        let mut cursor = current.walk();
5863        stack.extend(current.named_children(&mut cursor));
5864    }
5865    false
5866}
5867
5868fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5869    local_type_alias_name_node(node).or_else(|| match node.kind() {
5870        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5871            .child_by_field_name("name")
5872            .filter(|name| is_declaration_name(*name)),
5873        _ => None,
5874    })
5875}
5876
5877fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5878    loop {
5879        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5880            return Some(node);
5881        }
5882        node = node.parent()?;
5883    }
5884}
5885
5886fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5887    match node.kind() {
5888        "alias_declaration" => node.child_by_field_name("name"),
5889        "type_definition" => node
5890            .child_by_field_name("declarator")
5891            .and_then(declarator_name_node),
5892        _ => None,
5893    }
5894}
5895
5896fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5897    let mut current = alias.parent();
5898    while let Some(parent) = current {
5899        if matches!(
5900            parent.kind(),
5901            "class_specifier" | "struct_specifier" | "union_specifier"
5902        ) {
5903            return false;
5904        }
5905        if parent.kind() == "compound_statement" {
5906            return parent.start_byte() <= node.start_byte()
5907                && node.end_byte() <= parent.end_byte();
5908        }
5909        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5910            let Some(body) = parent.child_by_field_name("body") else {
5911                return false;
5912            };
5913            return node_is_within(body, alias) && node_is_within(body, node);
5914        }
5915        current = parent.parent();
5916    }
5917    false
5918}
5919
5920fn target_guided_missing_declaration_type_leaf<'tree>(
5921    node: Node<'tree>,
5922    ctx: &ScanCtx<'_>,
5923) -> Option<Node<'tree>> {
5924    if is_declaration_name(node) {
5925        return None;
5926    }
5927    let component_nodes = cpp_name_component_nodes(node)?;
5928    let name_node = component_nodes.last().copied()?;
5929    let name = node_text(name_node, ctx.source);
5930    if name != ctx.spec.target.identifier() {
5931        return None;
5932    }
5933    let inside_target_declaration = ctx
5934        .target_declaration_ranges
5935        .iter()
5936        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5937    if !inside_target_declaration
5938        && !ctx.visibility.external_type_candidate_visible_in_context(
5939            &ctx.analyzer,
5940            ctx.file,
5941            &ctx.spec.target,
5942            node,
5943        )
5944    {
5945        return None;
5946    }
5947    let components = component_nodes
5948        .iter()
5949        .map(|component| node_text(*component, ctx.source).to_string())
5950        .collect::<Vec<_>>();
5951    let local_alias_shadow = local_type_name_shadows(node, ctx);
5952    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5953    let indexed_alias_owner = ctx
5954        .analyzer
5955        .type_alias_provider()
5956        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5957        && member_alias_owner_matches_reference(node, ctx);
5958    let target_alias_self_reference = inside_target_declaration
5959        && ctx
5960            .analyzer
5961            .type_alias_provider()
5962            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5963    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5964        &ctx.analyzer,
5965        ctx.file,
5966        &ctx.spec.target,
5967        node,
5968    ) || member_alias_complete_class_context(node, ctx);
5969    if !target_alias_self_reference
5970        && !local_alias_shadow
5971        && member_alias_visible
5972        && (structured_alias_owner || indexed_alias_owner)
5973    {
5974        return Some(node);
5975    }
5976    let declaration = nearest_declaration_type_context(node)?;
5977    let candidates = visible_type_identifier_candidates(ctx, name);
5978    let unique_visible_target = !candidates.is_empty()
5979        && candidates
5980            .iter()
5981            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5982    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5983    let exact_scope_match = indexed_scope_matches_target_name(
5984        &indexed_scope,
5985        &components,
5986        is_globally_qualified_cpp_name(node),
5987        &ctx.spec.target,
5988    );
5989    if matches!(declaration.kind(), "field_declaration" | "declaration") {
5990        let parser_lost_declaration_scope =
5991            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5992                && unique_visible_target;
5993        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5994    }
5995    let lost_namespace_parameter_context =
5996        matches!(
5997            declaration.kind(),
5998            "parameter_declaration" | "optional_parameter_declaration"
5999        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
6000    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
6001        return Some(node);
6002    }
6003    None
6004}
6005
6006fn target_guided_missing_alias_rhs_type_leaf<'tree>(
6007    node: Node<'tree>,
6008    ctx: &ScanCtx<'_>,
6009) -> Option<Node<'tree>> {
6010    let mut stack = vec![node];
6011    while let Some(candidate) = stack.pop() {
6012        if candidate.kind() == "type_identifier"
6013            && !is_declaration_name(candidate)
6014            && matches!(
6015                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
6016                Some("template_type")
6017            )
6018        {
6019            let mut current = ctx.ancestry.parent(candidate);
6020            let mut saw_qualified = false;
6021            let mut saw_dependent = false;
6022            let mut saw_type_descriptor = false;
6023            let mut saw_alias_declaration = false;
6024            while let Some(ancestor) = current {
6025                match ancestor.kind() {
6026                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
6027                    "dependent_type" => saw_dependent = true,
6028                    "type_descriptor" => saw_type_descriptor = true,
6029                    "alias_declaration" => {
6030                        saw_alias_declaration = true;
6031                        break;
6032                    }
6033                    "template_type"
6034                    | "template_argument_list"
6035                    | "typename"
6036                    | "template_declaration" => {}
6037                    _ => {}
6038                }
6039                current = ctx.ancestry.parent(ancestor);
6040            }
6041            let name = node_text(candidate, ctx.source);
6042            let visible_candidates = visible_type_identifier_candidates(ctx, name);
6043            let canonical_alias_target = visible_candidates
6044                .iter()
6045                .filter_map(|alias| ctx.visibility.alias_target(alias))
6046                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
6047            let alias_resolves =
6048                ctx.visibility
6049                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
6050                    || canonical_alias_target;
6051            if saw_qualified
6052                && saw_dependent
6053                && saw_type_descriptor
6054                && saw_alias_declaration
6055                && alias_resolves
6056                && ctx.visibility.external_type_candidate_visible_in_context(
6057                    &ctx.analyzer,
6058                    ctx.file,
6059                    &ctx.spec.target,
6060                    candidate,
6061                )
6062            {
6063                return Some(candidate);
6064            }
6065        }
6066        push_named_children_reversed(candidate, &mut stack);
6067    }
6068    None
6069}
6070
6071fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
6072    let mut current = Some(node);
6073    while let Some(ancestor) = current {
6074        if matches!(
6075            ancestor.kind(),
6076            "field_declaration"
6077                | "parameter_declaration"
6078                | "optional_parameter_declaration"
6079                | "declaration"
6080                | "type_descriptor"
6081        ) {
6082            let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
6083            if contains_type
6084                && !(ancestor.kind() == "type_descriptor"
6085                    && is_cpp_template_argument_type_leaf(node))
6086            {
6087                return Some(ancestor);
6088            }
6089            if ancestor.kind() == "type_descriptor"
6090                && ancestor.parent().is_some_and(|parent| {
6091                    matches!(
6092                        parent.kind(),
6093                        "cast_expression"
6094                            | "new_expression"
6095                            | "sizeof_expression"
6096                            | "alignof_expression"
6097                            | "typeid_expression"
6098                    )
6099                })
6100            {
6101                return Some(ancestor);
6102            }
6103        }
6104        if matches!(
6105            ancestor.kind(),
6106            "compound_statement"
6107                | "translation_unit"
6108                | "namespace_definition"
6109                | "alias_declaration"
6110                | "type_definition"
6111                | "base_class_clause"
6112        ) {
6113            return None;
6114        }
6115        current = ancestor.parent();
6116    }
6117    None
6118}
6119
6120fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
6121    let mut candidates = Vec::new();
6122    for candidate in ctx
6123        .visibility
6124        .visible_identifier_candidates(ctx.file, name)
6125        .filter(|candidate| {
6126            candidate.is_class()
6127                || ctx
6128                    .analyzer
6129                    .type_alias_provider()
6130                    .is_some_and(|provider| provider.is_type_alias(candidate))
6131        })
6132    {
6133        if !candidates
6134            .iter()
6135            .any(|existing| same_logical_symbol(existing, candidate))
6136        {
6137            candidates.push(candidate.clone());
6138        }
6139    }
6140    candidates
6141}
6142
6143/// Recover a direct type-alias argument of `static_cast` when parser recovery
6144/// misclassifies a namespace alias as a local declaration. The indexed scope
6145/// and exact alias identity are required so a same-spelled alias in another
6146/// namespace remains excluded.
6147fn target_guided_static_cast_alias_type_descriptor<'tree>(
6148    node: Node<'tree>,
6149    ctx: &ScanCtx<'_>,
6150) -> Option<Node<'tree>> {
6151    if node.kind() != "type_descriptor" {
6152        return None;
6153    }
6154    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
6155        parent.kind() == "template_argument_list"
6156            && parent.named_child_count() == 1
6157            && parent.named_child(0) == Some(node)
6158    })?;
6159    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
6160        parent.kind() == "template_function"
6161            && parent.child_by_field_name("arguments") == Some(argument_list)
6162    })?;
6163    let name = template.child_by_field_name("name")?;
6164    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
6165        return None;
6166    }
6167    let target = &ctx.spec.target;
6168    if node_text(node, ctx.source) != target.identifier()
6169        || !ctx
6170            .analyzer
6171            .type_alias_provider()
6172            .is_some_and(|provider| provider.is_type_alias(target))
6173        || !ctx.visibility.is_physically_visible(ctx.file, target)
6174        || !ctx.visibility.external_type_candidate_visible_in_context(
6175            &ctx.analyzer,
6176            ctx.file,
6177            target,
6178            node,
6179        )
6180    {
6181        return None;
6182    }
6183
6184    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
6185    let target_scope = canonical_cpp_scope_components(target);
6186    let name_components = [target.identifier().to_string()];
6187    if !lexical_component_tiers(&name_components, false, &indexed_scope)
6188        .any(|components| components == target_scope)
6189    {
6190        return None;
6191    }
6192
6193    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
6194    if !candidates
6195        .iter()
6196        .any(|candidate| same_visible_symbol(candidate, target))
6197    {
6198        return None;
6199    }
6200    if candidates.iter().any(|candidate| {
6201        !same_visible_symbol(candidate, target)
6202            && lexical_component_tiers(&name_components, false, &indexed_scope)
6203                .any(|components| components == canonical_cpp_scope_components(candidate))
6204    }) {
6205        return None;
6206    }
6207    Some(node)
6208}
6209
6210fn indexed_scope_matches_target_name(
6211    indexed_scope: &[String],
6212    components: &[String],
6213    global: bool,
6214    target: &CodeUnit,
6215) -> bool {
6216    let target_name = cpp_name_for(target);
6217    lexical_component_tiers(components, global, indexed_scope)
6218        .any(|qualified| qualified.join("::") == target_name)
6219}
6220
6221fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
6222    // A global scope or one surviving name alone cannot distinguish a lost
6223    // namespace from a real global/unrelated reference. Require an enclosing
6224    // declaration component as well as a namespace component matching the
6225    // target's qualified scope.
6226    if target.package_name().is_empty() || indexed_scope.len() <= 1 {
6227        return false;
6228    }
6229    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6230        brokk_bifrost_core::analyzer::Language::Cpp,
6231        &cpp_name_for(target),
6232    );
6233    target_scope.pop();
6234    (1..indexed_scope.len())
6235        .rev()
6236        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
6237}
6238
6239fn indexed_enclosing_lexical_scope(
6240    analyzer: &CppGraphSource<'_>,
6241    file: &ProjectFile,
6242    node: Node<'_>,
6243) -> Option<Vec<String>> {
6244    let range = Range {
6245        start_byte: node.start_byte(),
6246        end_byte: node.end_byte(),
6247        start_line: node.start_position().row,
6248        end_line: node.end_position().row,
6249    };
6250    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6251    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6252        brokk_bifrost_core::analyzer::Language::Cpp,
6253        &cpp_name_for(&enclosing),
6254    );
6255    if !enclosing.is_class() && !enclosing.is_module() {
6256        components.pop();
6257    }
6258    Some(components)
6259}
6260
6261fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6262    if node.kind() != "qualified_identifier" {
6263        return None;
6264    }
6265    let mut stack = vec![node];
6266    while let Some(current) = stack.pop() {
6267        if current.kind() != "qualified_identifier" {
6268            continue;
6269        }
6270        if let Some(scope) = current.child_by_field_name("scope") {
6271            let text = qualified_scope_text(scope, ctx.source);
6272            if name_mentions(&text, &ctx.spec.member_name) {
6273                return Some(scope);
6274            }
6275        }
6276        let mut cursor = current.walk();
6277        for child in current.named_children(&mut cursor) {
6278            if child.kind() == "qualified_identifier" {
6279                stack.push(child);
6280            }
6281        }
6282    }
6283    None
6284}
6285
6286fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6287    let mut parts = vec![node_text(scope, source).to_string()];
6288    let mut current = scope.parent();
6289    while let Some(qualified) = current {
6290        let Some(parent) = qualified.parent() else {
6291            break;
6292        };
6293        if parent.kind() != "qualified_identifier"
6294            || parent.child_by_field_name("name") != Some(qualified)
6295        {
6296            break;
6297        }
6298        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6299            parts.push(node_text(outer_scope, source).to_string());
6300        }
6301        current = Some(parent);
6302    }
6303    parts.reverse();
6304    parts.join("::")
6305}
6306
6307fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6308    if node.kind() == "using_declaration" {
6309        maybe_record_using_callable_hit(node, ctx);
6310        return;
6311    }
6312    if has_ancestor_kind(node, "using_declaration") {
6313        return;
6314    }
6315    if node.kind() == "function_definition" {
6316        return;
6317    }
6318    if !matches!(
6319        node.kind(),
6320        "call_expression"
6321            | "new_expression"
6322            | "compound_literal_expression"
6323            | "declaration"
6324            | "field_initializer"
6325    ) {
6326        return;
6327    }
6328    let Some(owner) = ctx.spec.owner.as_ref() else {
6329        return;
6330    };
6331    if node.kind() == "field_initializer" {
6332        if !field_initializer_constructs_target(node, ctx, owner)
6333            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6334        {
6335            return;
6336        }
6337        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6338            match ctx
6339                .visibility
6340                .call_arity_evidence(ctx.file, node, ctx.source)
6341                .accepts(expected)
6342            {
6343                Some(true) => {}
6344                Some(false) => return,
6345                None => {
6346                    push_unproven_hit(node, ctx);
6347                    return;
6348                }
6349            }
6350        }
6351        match constructor_overload_selection(node, ctx) {
6352            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6353            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6354            ConstructorOverloadSelection::OtherOverload => {}
6355        }
6356        return;
6357    }
6358    if node.kind() == "declaration" {
6359        if declaration_is_object_construction_candidate(node, ctx)
6360            && declaration_mentions_type(node, ctx, owner)
6361            && ctx
6362                .spec
6363                .callable_arity_at(node.start_byte())
6364                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6365        {
6366            // The spelling names the owner in some configuration. The type
6367            // node's structured resolution applies this declaration's guards
6368            // to conditional alias arms, so an arm naming another class is not
6369            // this constructor's site (#6902).
6370            let structured = node.child_by_field_name("type").map(|type_node| {
6371                resolve_type_node_lexically_for_target(
6372                    type_node,
6373                    &ctx.analyzer,
6374                    ctx.visibility,
6375                    &ctx.ordinary_type_imports,
6376                    ctx.file,
6377                    ctx.source,
6378                    owner,
6379                    Some(&ctx.lexical_scope_cache),
6380                    ctx.recovered_sentinel_scope(type_node).as_deref(),
6381                )
6382            });
6383            match structured {
6384                Some(LexicalTypeResolution::Resolved {
6385                    unit, candidates, ..
6386                }) if !same_visible_symbol(&unit, owner)
6387                    && !candidates
6388                        .iter()
6389                        .any(|candidate| same_visible_symbol(candidate, owner)) =>
6390                {
6391                    return;
6392                }
6393                Some(LexicalTypeResolution::Ambiguous) => {
6394                    push_unproven_hit(node, ctx);
6395                    return;
6396                }
6397                Some(LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing)
6398                | None => {}
6399            }
6400            match constructor_overload_selection(node, ctx) {
6401                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6402                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6403                ConstructorOverloadSelection::OtherOverload => {}
6404            }
6405        }
6406        return;
6407    }
6408    let Some(type_node) = constructor_type_node(node) else {
6409        return;
6410    };
6411    let hit_node = function_terminal_node(type_node);
6412    let text = node_text(type_node, ctx.source);
6413    if !name_mentions(text, &ctx.spec.member_name) {
6414        return;
6415    }
6416    ctx.usages.note_raw_match();
6417    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6418        match ctx
6419            .visibility
6420            .call_arity_evidence(ctx.file, node, ctx.source)
6421            .accepts(expected)
6422        {
6423            Some(true) => {}
6424            Some(false) => return,
6425            None => {
6426                push_unproven_hit(hit_node, ctx);
6427                return;
6428            }
6429        }
6430    }
6431    match constructor_overload_selection(node, ctx) {
6432        ConstructorOverloadSelection::Target => {}
6433        ConstructorOverloadSelection::Ambiguous => {
6434            push_unproven_hit(hit_node, ctx);
6435            return;
6436        }
6437        ConstructorOverloadSelection::OtherOverload => return,
6438    }
6439    let structured_resolution = resolve_type_node_lexically_for_target(
6440        type_node,
6441        &ctx.analyzer,
6442        ctx.visibility,
6443        &ctx.ordinary_type_imports,
6444        ctx.file,
6445        ctx.source,
6446        owner,
6447        Some(&ctx.lexical_scope_cache),
6448        ctx.recovered_sentinel_scope(type_node).as_deref(),
6449    );
6450    let structurally_resolves = matches!(
6451        &structured_resolution,
6452        LexicalTypeResolution::Resolved {
6453            unit, candidates, ..
6454        } if same_visible_symbol(unit, owner)
6455            || candidates
6456                .iter()
6457                .any(|candidate| same_visible_symbol(candidate, owner))
6458    );
6459    if structurally_resolves
6460        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6461            && ctx
6462                .visibility
6463                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6464    {
6465        push_hit(hit_node, ctx);
6466    } else {
6467        push_unproven_hit(hit_node, ctx);
6468    }
6469}
6470
6471/// What the overload filter says about a construction site relative to the
6472/// constructor being scanned.
6473enum ConstructorOverloadSelection {
6474    /// The arguments leave the scan target as the only viable constructor, or
6475    /// the filter has no evidence to apply here. Either way nothing about the
6476    /// arguments argues against the site, so the rest of the scan decides it.
6477    Target,
6478    /// More than one of the owner's constructors stays viable, so the site
6479    /// cannot be proven to belong to the target.
6480    Ambiguous,
6481    /// The arguments select a sibling constructor and not the target.
6482    OtherOverload,
6483}
6484
6485/// Which of the owner's constructors the arguments at a construction site
6486/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6487/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6488/// `T var(args)` declaration.
6489///
6490/// This is the inverse reading of the forward direction's overload choice. The
6491/// candidate set is the owner's arity-compatible constructors, the arguments are
6492/// typed by the same `expression_arg_type` the method and free-function scan
6493/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6494/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6495/// arity alone made a scan of one overload claim every same-arity sibling's call
6496/// (#2908).
6497fn constructor_overload_selection(
6498    node: Node<'_>,
6499    ctx: &ScanCtx<'_>,
6500) -> ConstructorOverloadSelection {
6501    let Some(owner) = ctx.spec.owner.as_ref() else {
6502        return ConstructorOverloadSelection::Target;
6503    };
6504    if ctx.spec.param_types.is_none() {
6505        return ConstructorOverloadSelection::Target;
6506    }
6507    // A `T var(args)` declaration keeps its arguments under the declarator or the
6508    // init declarator's value; every other shape spells an argument list that the
6509    // shared call helpers find, and its argument count needs the macro-aware
6510    // arity evidence.
6511    let (arity, arg_types) = if node.kind() == "declaration" {
6512        match declaration_constructor_initializer(node) {
6513            DeclarationConstructorInitializer::Arguments(arguments) => (
6514                argument_children(arguments).count(),
6515                argument_list_types(arguments, ctx),
6516            ),
6517            DeclarationConstructorInitializer::Expression(value) => {
6518                (1, vec![expression_arg_type(value, ctx)])
6519            }
6520            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6521        }
6522    } else {
6523        let Some(arity) = ctx
6524            .visibility
6525            .call_arity_evidence(ctx.file, node, ctx.source)
6526            .exact()
6527        else {
6528            return ConstructorOverloadSelection::Target;
6529        };
6530        (arity, call_argument_types(node, ctx))
6531    };
6532    let mut candidates = ctx
6533        .visibility
6534        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6535        .into_iter()
6536        .filter(|unit| unit.is_function())
6537        .cloned()
6538        .collect::<Vec<_>>();
6539    candidates.retain(|unit| {
6540        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6541    });
6542    if !candidates
6543        .iter()
6544        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6545    {
6546        return ConstructorOverloadSelection::Target;
6547    }
6548    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6549        candidates,
6550        &arg_types,
6551        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6552        &|candidate| {
6553            ctx.visibility
6554                .callable_is_template_declaration(&ctx.analyzer, candidate)
6555        },
6556        &|name| ctx.visibility.resolve_type(ctx.file, name),
6557        &|left, right| same_visible_symbol(left, right),
6558    );
6559    if !filtered
6560        .iter()
6561        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6562    {
6563        return ConstructorOverloadSelection::OtherOverload;
6564    }
6565    // A header declaration and its out-of-line body are one constructor even
6566    // when their persisted signature strings spell a parameter differently, so
6567    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6568    if filtered.iter().all(|candidate| {
6569        ctx.visibility
6570            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6571    }) {
6572        ConstructorOverloadSelection::Target
6573    } else {
6574        ConstructorOverloadSelection::Ambiguous
6575    }
6576}
6577
6578fn unqualified_base_initializer_constructs_target(
6579    node: Node<'_>,
6580    ctx: &ScanCtx<'_>,
6581    target_owner: &CodeUnit,
6582) -> bool {
6583    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6584        return false;
6585    }
6586    let Some(name) = node
6587        .child_by_field_name("name")
6588        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6589    else {
6590        return false;
6591    };
6592    if node_text(name, ctx.source) != ctx.spec.member_name {
6593        return false;
6594    }
6595    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6596        return false;
6597    };
6598    let inherited = ctx.visibility.inherited_injected_class_owner(
6599        &ctx.analyzer,
6600        ctx.file,
6601        &enclosing_owner,
6602        &ctx.spec.member_name,
6603    );
6604    inherited.is_some_and(|owner| {
6605        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6606    })
6607}
6608
6609fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6610    if node.kind() == "function_definition" {
6611        maybe_record_free_function_definition_hit(node, ctx);
6612        return;
6613    }
6614    if node.kind() == "function_declarator" {
6615        maybe_record_recovered_error_free_function_call(node, ctx);
6616        return;
6617    }
6618    if node.kind() == "identifier" {
6619        maybe_record_free_function_value_reference(node, ctx);
6620        return;
6621    }
6622    if node.kind() != "call_expression" {
6623        return;
6624    }
6625    let Some(function) = node
6626        .child_by_field_name("function")
6627        .or_else(|| node.named_child(0))
6628    else {
6629        return;
6630    };
6631    let text = node_text(function, ctx.source);
6632    if !name_matches_callable(text, &ctx.spec.member_name) {
6633        return;
6634    }
6635    ctx.usages.note_raw_match();
6636    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6637        match ctx
6638            .visibility
6639            .call_arity_evidence(ctx.file, node, ctx.source)
6640            .accepts(expected)
6641        {
6642            Some(true) => {}
6643            Some(false) => return,
6644            None => {
6645                // The argument count is unknown after macro expansion. It still
6646                // cannot select a *different* target when the bare name binds
6647                // to exactly one visible callable, so let the bare-call
6648                // resolution below prove that site; every other shape stays
6649                // unproven (#1811, the scan side of the same over-conservatism
6650                // that made the forward answer discard its lone candidate).
6651                if !bare_name_binds_only_target(node, function, text, ctx) {
6652                    push_unproven_hit(function_terminal_node(function), ctx);
6653                    return;
6654                }
6655            }
6656        }
6657    }
6658    if matches!(function.kind(), "identifier" | "template_function") {
6659        let terminal = function_terminal_node(function);
6660        let name = node_text(terminal, ctx.source);
6661        if ctx.local_shadows.is_shadowed(name) {
6662            return;
6663        }
6664        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6665            && !matches!(
6666                resolve_declaring_member_owner(
6667                    &ctx.analyzer,
6668                    ctx.visibility,
6669                    ctx.file,
6670                    &enclosing_owner,
6671                    name,
6672                ),
6673                EnclosingMemberOwnerResolution::Missing
6674            )
6675        {
6676            return;
6677        }
6678        match resolve_bare_call_target(
6679            node,
6680            function,
6681            &ctx.analyzer,
6682            ctx.visibility,
6683            &ctx.ordinary_type_imports,
6684            ctx.file,
6685            ctx.source,
6686        ) {
6687            BareCallTargetResolution::FreeFunctions(units)
6688                if units
6689                    .iter()
6690                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6691            {
6692                if free_function_call_may_target(node, text, ctx) {
6693                    let recursive = enclosing_context(terminal, ctx)
6694                        .enclosing
6695                        .as_ref()
6696                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6697                    if recursive {
6698                        push_recursive_reference_hit(terminal, ctx);
6699                    } else {
6700                        push_hit(terminal, ctx);
6701                    }
6702                }
6703            }
6704            BareCallTargetResolution::UnprovenFreeFunctions(units)
6705                if units
6706                    .iter()
6707                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6708            {
6709                push_unproven_hit(terminal, ctx);
6710            }
6711            BareCallTargetResolution::FreeFunctions(_)
6712            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6713            | BareCallTargetResolution::Type(_)
6714            | BareCallTargetResolution::CallableShadow => {}
6715            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6716                push_unproven_hit(terminal, ctx);
6717            }
6718        }
6719        return;
6720    }
6721    if !free_function_call_may_target(node, text, ctx) {
6722        return;
6723    }
6724    if ctx.visibility.contains_named_symbol(
6725        ctx.file,
6726        text,
6727        TargetKind::FreeFunction,
6728        &ctx.spec.target,
6729    ) {
6730        push_hit(function_terminal_node(function), ctx);
6731    } else if ctx.visibility.resolve_known_non_target(
6732        ctx.file,
6733        text,
6734        TargetKind::FreeFunction,
6735        &ctx.spec.target,
6736    ) {
6737        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6738        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6739    } else {
6740        push_unproven_hit(function_terminal_node(function), ctx);
6741    }
6742}
6743
6744fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6745    same_visible_symbol(unit, &ctx.spec.target)
6746        || ctx
6747            .visibility
6748            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6749            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6750                &ctx.analyzer,
6751                ctx.visibility,
6752                unit,
6753                &ctx.spec.target,
6754            )
6755}
6756
6757/// Recover a bare call whose adjacent object-like macro arguments made
6758/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6759///
6760/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6761/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6762/// real block-scope function declaration remains under a `declaration`, so the
6763/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6764/// out of declaration syntax. The malformed parameter list cannot establish
6765/// arity; publish a proven hit only when ordinary visibility leaves one
6766/// callable identity for the bare name.
6767fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6768    // The macro-declaration grammar keeps the same call as a bogus
6769    // `function_definition` whose damaged declarator ends at the block
6770    // literal's `(`: its parameter list is the call's argument prefix, so
6771    // unlike the ERROR shape it carries countable arity evidence.
6772    let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6773    if !block_call_envelope
6774        && (ctx
6775            .ancestry
6776            .parent(node)
6777            .is_none_or(|parent| parent.kind() != "ERROR")
6778            || !has_ancestor_kind(node, "compound_statement"))
6779    {
6780        return;
6781    }
6782    let mut recovered_functions = Vec::new();
6783    if let Some(function) = node
6784        .child_by_field_name("declarator")
6785        .filter(|function| function.kind() == "identifier")
6786    {
6787        recovered_functions.push(function);
6788    }
6789    if let Some(parameters) = node.child_by_field_name("parameters") {
6790        let mut cursor = parameters.walk();
6791        for parameter in parameters
6792            .named_children(&mut cursor)
6793            .filter(|child| child.kind() == "parameter_declaration")
6794        {
6795            if parameter
6796                .child_by_field_name("declarator")
6797                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6798                && let Some(function) = parameter
6799                    .child_by_field_name("type")
6800                    .filter(|function| function.kind() == "type_identifier")
6801            {
6802                recovered_functions.push(function);
6803            }
6804        }
6805    }
6806
6807    for function in recovered_functions {
6808        let name = node_text(function, ctx.source);
6809        if !name_matches_callable(name, &ctx.spec.member_name)
6810            || ctx.local_shadows.is_shadowed(name)
6811        {
6812            continue;
6813        }
6814        ctx.usages.note_raw_match();
6815        if block_call_envelope
6816            && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6817            && ctx
6818                .visibility
6819                .call_arity_evidence(ctx.file, node, ctx.source)
6820                .accepts(expected)
6821                == Some(false)
6822        {
6823            continue;
6824        }
6825        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6826            push_hit(function, ctx);
6827            continue;
6828        }
6829        let mut candidates =
6830            ctx.visibility
6831                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6832        candidates.retain(|candidate| {
6833            ctx.visibility.declaration_visible_at(
6834                &ctx.analyzer,
6835                ctx.file,
6836                candidate,
6837                function.start_byte(),
6838            )
6839        });
6840        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6841        if candidates
6842            .iter()
6843            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6844        {
6845            push_unproven_hit(function, ctx);
6846        }
6847    }
6848}
6849
6850/// Whether the bare name at `call` binds to exactly one visible callable, and
6851/// that callable is the scan target.
6852///
6853/// This is the scan-side reading of the #1811 rule: with one name binding there
6854/// is nothing an unknown argument count could select instead, so the site is a
6855/// proven reference rather than an unproven one. Only bare identifiers qualify;
6856/// a member or qualified call reaches its target through a receiver this cannot
6857/// judge.
6858fn bare_name_binds_only_target(
6859    call: Node<'_>,
6860    function: Node<'_>,
6861    text: &str,
6862    ctx: &ScanCtx<'_>,
6863) -> bool {
6864    if !matches!(function.kind(), "identifier" | "template_function") {
6865        return false;
6866    }
6867    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6868}
6869
6870fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6871    let mut candidates = ctx
6872        .visibility
6873        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6874    candidates.retain(|candidate| {
6875        ctx.visibility
6876            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6877    });
6878    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6879    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6880}
6881
6882fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6883    if ctx.spec.param_types.is_none() {
6884        return true;
6885    }
6886    let mut candidates = ctx
6887        .visibility
6888        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6889    candidates.retain(|candidate| {
6890        ctx.visibility
6891            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6892    });
6893    callable_candidates_may_match_target(call, candidates, ctx)
6894}
6895
6896fn callable_candidates_may_match_target(
6897    call: Node<'_>,
6898    mut candidates: Vec<CodeUnit>,
6899    ctx: &ScanCtx<'_>,
6900) -> bool {
6901    let Some(arity) = ctx
6902        .visibility
6903        .call_arity_evidence(ctx.file, call, ctx.source)
6904        .exact()
6905    else {
6906        return true;
6907    };
6908    candidates.retain(|unit| {
6909        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6910    });
6911    if candidates.is_empty()
6912        || !candidates
6913            .iter()
6914            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6915    {
6916        return true;
6917    }
6918    let arg_types = call_argument_types(call, ctx);
6919    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6920        candidates,
6921        &arg_types,
6922        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6923        &|candidate| {
6924            ctx.visibility
6925                .callable_is_template_declaration(&ctx.analyzer, candidate)
6926        },
6927        &|name| ctx.visibility.resolve_type(ctx.file, name),
6928        &|left, right| same_visible_symbol(left, right),
6929    );
6930    filtered
6931        .iter()
6932        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6933}
6934
6935/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6936/// in order.
6937///
6938/// A `None` entry is an argument the index cannot type; one of them makes
6939/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6940fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6941    argument_children(arguments)
6942        .map(|arg| expression_arg_type(arg, ctx))
6943        .collect()
6944}
6945
6946fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6947    call_arguments_node(call)
6948        .map(|arguments| argument_list_types(arguments, ctx))
6949        .unwrap_or_default()
6950}
6951
6952fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6953    match node.kind() {
6954        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6955        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6956            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6957            literal
6958        }),
6959        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6960        // `T v(x);` is the vexing parse: the grammar reads it as a function
6961        // declaration whose one parameter is `x` spelled as a type. A parameter
6962        // declaration that is nothing but a type identifier is that reading, and
6963        // the identifier it holds is the constructor argument.
6964        "parameter_declaration" => {
6965            let declared = node.child_by_field_name("type")?;
6966            (node.child_by_field_name("declarator").is_none()
6967                && declared.kind() == "type_identifier")
6968                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6969                .flatten()
6970        }
6971        // `this->m_params` names the same data member as the bare `m_params`.
6972        "field_expression" => {
6973            let receiver = node
6974                .child_by_field_name("argument")
6975                .or_else(|| node.named_child(0))?;
6976            let field = node.child_by_field_name("field")?;
6977            (receiver.kind() == "this")
6978                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6979                .flatten()
6980        }
6981        "parenthesized_expression" => node
6982            .child_by_field_name("argument")
6983            .or_else(|| node.named_child(0))
6984            .and_then(|inner| expression_arg_type(inner, ctx)),
6985        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
6986        // forward (#2552).
6987        "call_expression" => {
6988            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6989            expression_arg_type(forwarded, ctx)
6990        }
6991        "pointer_expression" => {
6992            let delta = match node.child_by_field_name("operator")?.kind() {
6993                "&" => 1,
6994                "*" => -1,
6995                _ => return None,
6996            };
6997            let inner = node
6998                .child_by_field_name("argument")
6999                .or_else(|| node.named_child(0))?;
7000            let mut arg_type = expression_arg_type(inner, ctx)?;
7001            arg_type.indirection += delta;
7002            Some(arg_type)
7003        }
7004        _ => None,
7005    }
7006}
7007
7008/// The type of a bare value name used as a call argument: the local or
7009/// parameter binding when one is in scope, otherwise the enclosing class's data
7010/// member of that name.
7011///
7012/// Unqualified lookup reaches a local before a data member, so a local of the
7013/// same name ends the search rather than falling through to the field. This
7014/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
7015/// the two directions agreeing about which overload a member-field argument
7016/// selects (#2894).
7017fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
7018    match ctx.bindings.resolve_symbol(name) {
7019        SymbolResolution::Precise(bindings) => {
7020            bindings.iter().find_map(CppScanBinding::as_arg_type)
7021        }
7022        SymbolResolution::Ambiguous => None,
7023        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
7024            .then(|| enclosing_member_field_arg_type(node, name, ctx))
7025            .flatten(),
7026    }
7027}
7028
7029/// The declared type of `name` read as a data member of the class that lexically
7030/// owns `node`.
7031///
7032/// `None` when there is no enclosing class, when the name is not one data member
7033/// of it, or when the member's declared type does not resolve. An unknown
7034/// argument type makes the overload filter keep every candidate, which is the
7035/// honest answer for a field the index cannot type.
7036fn enclosing_member_field_arg_type(
7037    node: Node<'_>,
7038    name: &str,
7039    ctx: &ScanCtx<'_>,
7040) -> Option<CppArgType> {
7041    let owner = enclosing_context(node, ctx).owner?;
7042    let fields = ctx
7043        .visibility
7044        .visible_members_for_owner_name(ctx.file, &owner, name)
7045        .into_iter()
7046        .filter(|unit| unit.is_field())
7047        .collect::<Vec<_>>();
7048    let [field] = fields.as_slice() else {
7049        return None;
7050    };
7051    let (type_name, unit, indirection) =
7052        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
7053    Some(CppArgType {
7054        name: type_name,
7055        unit,
7056        indirection,
7057        pointee_const: false,
7058    })
7059}
7060
7061/// Record a *non-call* reference to a free function used as a value: `&foo`,
7062/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
7063/// `foo()` is recorded by the call_expression arm, and the function's own
7064/// declaration/definition name is not a reference.
7065fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7066    let text = node_text(node, ctx.source);
7067    if !name_matches_callable(text, &ctx.spec.member_name) {
7068        return;
7069    }
7070    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
7071        ctx.usages.note_raw_match();
7072        match kind {
7073            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
7074            RecoveredCallableDeclarationKind::Definition => {
7075                push_recovered_definition_hit(node, ctx)
7076            }
7077        }
7078        return;
7079    }
7080    if is_declaration_name(node) {
7081        maybe_record_free_function_declaration_reference(node, ctx);
7082        return;
7083    }
7084    if is_call_callee_node(node) {
7085        return;
7086    }
7087    ctx.usages.note_raw_match();
7088    if ctx.visibility.contains_named_symbol(
7089        ctx.file,
7090        text,
7091        TargetKind::FreeFunction,
7092        &ctx.spec.target,
7093    ) {
7094        push_hit(node, ctx);
7095    } else if ctx.visibility.resolve_known_non_target(
7096        ctx.file,
7097        text,
7098        TargetKind::FreeFunction,
7099        &ctx.spec.target,
7100    ) {
7101        // A qualified reference proven to a different namespace is not a match.
7102    } else {
7103        push_unproven_hit(node, ctx);
7104    }
7105}
7106
7107#[derive(Clone, Copy)]
7108enum RecoveredCallableDeclarationKind {
7109    Declaration,
7110    Definition,
7111}
7112
7113/// Recover the role of a callable name whose declaration was split by an
7114/// ERROR node. An indexed declaration range proves identity when available;
7115/// for a top-level C callable that recovery failed to range-index, same-file
7116/// name identity is sufficient because C has no function overloading. The CST
7117/// shape distinguishes a prototype from a definition. C++ parsing of
7118/// an unknown C return type can put the name under an ERROR child of the
7119/// function declarator, while call-shaped recovery puts that declarator under
7120/// an ERROR whose parent is a compound statement; only the former is admitted.
7121fn recovered_callable_declaration_kind(
7122    node: Node<'_>,
7123    ctx: &ScanCtx<'_>,
7124) -> Option<RecoveredCallableDeclarationKind> {
7125    if !matches!(node.kind(), "identifier" | "field_identifier") {
7126        return None;
7127    }
7128    let target_identity_proven =
7129        ctx.target_declaration_ranges.iter().any(|range| {
7130            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7131        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
7132            && ctx.spec.target.is_function()
7133            && ctx.spec.target.source() == ctx.file);
7134    let mut current = ctx.ancestry.parent(node);
7135    while let Some(parent) = current {
7136        if parent.is_error() {
7137            // C files with an unknown return type can retain the real
7138            // callable name as `function_declarator > ERROR`, with the ERROR
7139            // as a named child rather than the declarator field itself.
7140            let error = parent;
7141            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
7142                function.kind() == "function_declarator"
7143                    && function
7144                        .child_by_field_name("parameters")
7145                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
7146            }) else {
7147                current = ctx.ancestry.parent(parent);
7148                continue;
7149            };
7150            let container = ctx.ancestry.parent(function)?;
7151            let kind = match container.kind() {
7152                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7153                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7154                _ => return None,
7155            };
7156            return target_identity_proven.then_some(kind);
7157        }
7158        if parent.kind() == "function_declarator" {
7159            let error = ctx
7160                .ancestry
7161                .parent(parent)
7162                .filter(|parent| parent.is_error())?;
7163            let declarator = parent.child_by_field_name("declarator")?;
7164            if declarator.start_byte() > node.start_byte()
7165                || node.end_byte() > declarator.end_byte()
7166            {
7167                return None;
7168            }
7169            let container = ctx.ancestry.parent(error)?;
7170            let kind = match container.kind() {
7171                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7172                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7173                _ => return None,
7174            };
7175            return target_identity_proven.then_some(kind);
7176        }
7177        if parent.is_error() {
7178            current = ctx.ancestry.parent(parent);
7179            continue;
7180        }
7181        if matches!(
7182            parent.kind(),
7183            "translation_unit" | "function_definition" | "compound_statement"
7184        ) {
7185            return None;
7186        }
7187        current = ctx.ancestry.parent(parent);
7188    }
7189    None
7190}
7191
7192fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7193    if !ctx.spec.callable_has_definition_body {
7194        return;
7195    }
7196    let text = node_text(node, ctx.source);
7197    if !name_matches_callable(text, &ctx.spec.member_name) {
7198        return;
7199    }
7200    let mut declaration = ctx.ancestry.parent(node);
7201    while let Some(candidate) = declaration {
7202        if candidate.kind() == "function_definition" {
7203            return;
7204        }
7205        if candidate.kind() == "declaration" {
7206            break;
7207        }
7208        declaration = ctx.ancestry.parent(candidate);
7209    }
7210    if declaration.is_none() {
7211        return;
7212    }
7213    let linked_declaration = ctx
7214        .visibility
7215        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
7216        .into_iter()
7217        .find(|candidate| {
7218            candidate.source() == ctx.file
7219                && candidate.is_function()
7220                && (!ctx.target_group.contains(candidate)
7221                    || candidate.source() == ctx.spec.target.source())
7222                && ctx.analyzer.ranges(candidate).iter().any(|range| {
7223                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7224                })
7225                && (candidate.source() == ctx.spec.target.source()
7226                    && candidate.fq_name() == ctx.spec.target.fq_name()
7227                    && candidate.signature() == ctx.spec.target.signature()
7228                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
7229                        &ctx.analyzer,
7230                        ctx.visibility,
7231                        candidate,
7232                        &ctx.spec.target,
7233                    ))
7234                && callable_arity_matches_target(candidate, node, ctx)
7235        });
7236    if linked_declaration.is_none() {
7237        return;
7238    }
7239    ctx.usages.note_raw_match();
7240    push_declaration_reference_hit(node, ctx);
7241}
7242
7243fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7244    let Some(function) = function_definition_name_node(node) else {
7245        return;
7246    };
7247    let text = node_text(function, ctx.source);
7248    if !name_matches_callable(text, &ctx.spec.member_name) {
7249        return;
7250    }
7251    ctx.usages.note_raw_match();
7252    if !function_definition_signature_matches_target(node, ctx) {
7253        return;
7254    }
7255    match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
7256        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7257        DefinitionNameTargetResolution::KnownNonTarget => {
7258            // A definition in another explicit namespace is a proven non-match.
7259        }
7260        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7261    }
7262}
7263
7264fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7265    if node.kind() == "preproc_arg" {
7266        maybe_record_function_macro_replacement_method_hits(node, ctx);
7267        return;
7268    }
7269    if node.kind() == "using_declaration" {
7270        maybe_record_using_callable_hit(node, ctx);
7271        return;
7272    }
7273    if has_ancestor_kind(node, "using_declaration") {
7274        return;
7275    }
7276    if node.kind() == "function_definition" {
7277        maybe_record_method_definition_hit(node, ctx);
7278        return;
7279    }
7280    if is_declaration_name(node) {
7281        return;
7282    }
7283    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7284        maybe_record_qualified_method_value_hit(node, member, ctx);
7285        return;
7286    }
7287    if let Some(value) = qualified_callable_value(node) {
7288        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7289        return;
7290    }
7291    if let Some(call) = recovered_relational_template_member_call(node) {
7292        maybe_record_recovered_relational_template_method_hit(call, ctx);
7293        return;
7294    }
7295    if node.kind() != "call_expression" {
7296        return;
7297    }
7298    if let Some((receiver, operator)) = explicit_operator_call(node) {
7299        let text = node_text(operator, ctx.source);
7300        if !name_matches_callable(text, &ctx.spec.member_name) {
7301            return;
7302        }
7303        ctx.usages.note_raw_match();
7304        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7305            match ctx
7306                .visibility
7307                .call_arity_evidence(ctx.file, node, ctx.source)
7308                .accepts(expected)
7309            {
7310                Some(true) => {}
7311                Some(false) => return,
7312                None => {
7313                    push_unproven_hit(operator, ctx);
7314                    return;
7315                }
7316            }
7317        }
7318        let arity = ctx
7319            .visibility
7320            .call_arity_evidence(ctx.file, node, ctx.source)
7321            .exact();
7322        record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7323        return;
7324    }
7325    let Some(function) = node
7326        .child_by_field_name("function")
7327        .or_else(|| node.named_child(0))
7328    else {
7329        return;
7330    };
7331    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7332        return;
7333    }
7334    if function.kind() == "identifier"
7335        && ctx
7336            .local_shadows
7337            .is_shadowed(node_text(function, ctx.source))
7338    {
7339        return;
7340    }
7341    ctx.usages.note_raw_match();
7342    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7343        match ctx
7344            .visibility
7345            .call_arity_evidence(ctx.file, node, ctx.source)
7346            .accepts(expected)
7347        {
7348            Some(true) => {}
7349            Some(false) => return,
7350            None => {
7351                push_unproven_hit(function_terminal_node(function), ctx);
7352                return;
7353            }
7354        }
7355    }
7356    if !method_call_may_target(node, ctx) {
7357        return;
7358    }
7359    if is_structurally_qualified(function) {
7360        match qualified_owner_resolution(function, ctx) {
7361            QualifiedOwnerResolution::Target => {
7362                push_hit(function_terminal_node(function), ctx);
7363            }
7364            QualifiedOwnerResolution::NonTarget => {}
7365            QualifiedOwnerResolution::Unresolved => {
7366                push_unproven_hit(function_terminal_node(function), ctx);
7367            }
7368        }
7369        return;
7370    }
7371    match call_function_target_resolution(function, ctx) {
7372        MethodReceiverTargetResolution::Target
7373            if call_function_has_direct_self_receiver(
7374                function,
7375                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7376            ) =>
7377        {
7378            push_self_receiver_hit(function_terminal_node(function), ctx);
7379        }
7380        MethodReceiverTargetResolution::Target => {
7381            push_hit(function_terminal_node(function), ctx);
7382        }
7383        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7384        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7385        // the target member declared on a base is a genuine external usage of that inherited
7386        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7387        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7388        // arm because `same_owner_context` also accepts this inherited case.
7389        MethodReceiverTargetResolution::Missing
7390            if inherited_target_owner_context(function, ctx) =>
7391        {
7392            push_hit(function_terminal_node(function), ctx);
7393        }
7394        MethodReceiverTargetResolution::Missing
7395            if (matches!(function.kind(), "identifier" | "template_function")
7396                || call_function_has_direct_self_receiver(
7397                    function,
7398                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7399                ))
7400                && (same_owner_context(function, ctx)
7401                    || out_of_line_target_owner_context(function, ctx)) =>
7402        {
7403            push_self_receiver_hit(function_terminal_node(function), ctx);
7404        }
7405        MethodReceiverTargetResolution::Missing
7406            if function.kind() == "identifier"
7407                && resolves_to_lexical_free_function(function, ctx) =>
7408        {
7409            // A visible namespace/free function is a proven negative once the
7410            // enclosing structured owner and its hierarchy contain no such member.
7411        }
7412        MethodReceiverTargetResolution::Missing
7413            if !receiver_has_known_non_target(function, ctx)
7414                && !known_non_target_owner_context(function, ctx) =>
7415        {
7416            push_unproven_hit(function_terminal_node(function), ctx);
7417        }
7418        MethodReceiverTargetResolution::Missing => {}
7419    }
7420}
7421
7422/// Report member calls written inside a function-like macro's replacement.
7423///
7424/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7425/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7426/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7427/// structure, and every hit is reported at the bytes the member spells inside
7428/// that token.
7429fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7430    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7431        parent.kind() == "preproc_function_def"
7432            && parent
7433                .child_by_field_name("value")
7434                .is_some_and(|value| same_node(value, node))
7435    }) else {
7436        return;
7437    };
7438    let Some(body) = ctx
7439        .visibility
7440        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7441    else {
7442        return;
7443    };
7444    let Some(statements) = body.statements() else {
7445        return;
7446    };
7447    // Collect the candidate calls before typing anything: a replacement that
7448    // never names the queried member must not pay for its declarations.
7449    let mut candidates = Vec::new();
7450    let mut stack = vec![statements];
7451    while let Some(current) = stack.pop() {
7452        push_named_children_reversed(current, &mut stack);
7453        if current.kind() != "call_expression" {
7454            continue;
7455        }
7456        let Some(function) = current
7457            .child_by_field_name("function")
7458            .filter(|function| function.kind() == "field_expression")
7459        else {
7460            continue;
7461        };
7462        let Some(member) = function.child_by_field_name("field") else {
7463            continue;
7464        };
7465        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7466            continue;
7467        }
7468        let Some(receiver) = function
7469            .child_by_field_name("argument")
7470            .or_else(|| function.child_by_field_name("object"))
7471            .or_else(|| function.named_child(0))
7472        else {
7473            continue;
7474        };
7475        candidates.push((current, member, receiver));
7476    }
7477    if candidates.is_empty() {
7478        return;
7479    }
7480    let Some(replacement_start) =
7481        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7482    else {
7483        return;
7484    };
7485    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7486    for (current, member, receiver) in candidates {
7487        if ctx.usages.limit_reached() {
7488            return;
7489        }
7490        let range = body.file_range(member, replacement_start);
7491        debug_assert_eq!(
7492            ctx.source.get(range.clone()),
7493            Some(node_text(member, &body.source)),
7494            "macro replacement member range must spell the member name"
7495        );
7496        ctx.usages.note_raw_match();
7497        // The replacement's own macro environment is the one at the definition,
7498        // not at the sentinel offsets the recovered call carries.
7499        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7500            ctx.file,
7501            current,
7502            &body.source,
7503            definition.start_byte(),
7504        );
7505        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7506            match arity_evidence.accepts(expected) {
7507                Some(true) => {}
7508                Some(false) => continue,
7509                None => {
7510                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7511                    continue;
7512                }
7513            }
7514        }
7515        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7516        {
7517            MacroReplacementReceiver::Parameter => {
7518                if target_member_is_visible_candidate(ctx) {
7519                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7520                }
7521                continue;
7522            }
7523            MacroReplacementReceiver::Unknown => continue,
7524            MacroReplacementReceiver::Units(units) => {
7525                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7526            }
7527        };
7528        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7529            MethodReceiverTargetResolution::Target => {
7530                push_reference_hit_range(node, range.start, range.end, ctx);
7531            }
7532            MethodReceiverTargetResolution::Missing => {
7533                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7534            }
7535            MethodReceiverTargetResolution::NonTarget
7536            | MethodReceiverTargetResolution::Ambiguous => {}
7537        }
7538    }
7539}
7540
7541enum MacroReplacementReceiver {
7542    /// The receiver is one of the macro's parameters. Its type is supplied by
7543    /// each invocation, so a member call on it can never be proven here.
7544    Parameter,
7545    /// The receiver's declared type resolved to these units.
7546    Units(Vec<CodeUnit>),
7547    /// The receiver has a shape this path does not type.
7548    Unknown,
7549}
7550
7551fn macro_replacement_receiver(
7552    receiver: Node<'_>,
7553    body: &ParsedReplacementBody,
7554    locals: &HashMap<String, Option<CodeUnit>>,
7555    anchor: Node<'_>,
7556    ctx: &ScanCtx<'_>,
7557) -> MacroReplacementReceiver {
7558    let mut current = receiver;
7559    while matches!(
7560        current.kind(),
7561        "parenthesized_expression" | "pointer_expression"
7562    ) {
7563        let Some(inner) = current
7564            .child_by_field_name("argument")
7565            .or_else(|| current.named_child(0))
7566        else {
7567            return MacroReplacementReceiver::Unknown;
7568        };
7569        current = inner;
7570    }
7571    if !matches!(current.kind(), "identifier" | "field_identifier") {
7572        return MacroReplacementReceiver::Unknown;
7573    }
7574    let name = node_text(current, &body.source);
7575    if body.parameters.iter().any(|parameter| parameter == name) {
7576        return MacroReplacementReceiver::Parameter;
7577    }
7578    if let Some(local) = locals.get(name) {
7579        return match local {
7580            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7581            None => MacroReplacementReceiver::Unknown,
7582        };
7583    }
7584    // Neither a parameter nor a replacement-local: an ordinary name the macro
7585    // definition site sees, resolved exactly as the ordinary receiver path
7586    // resolves a bare identifier.
7587    let global_fields = ctx
7588        .visibility
7589        .visible_identifier_candidates(ctx.file, name)
7590        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7591        .collect::<Vec<_>>();
7592    if !global_fields.is_empty() {
7593        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7594            global_fields,
7595            anchor,
7596            ctx,
7597        ));
7598    }
7599    MacroReplacementReceiver::Units(
7600        ctx.visibility
7601            .resolve_type(ctx.file, name)
7602            .into_iter()
7603            .collect(),
7604    )
7605}
7606
7607/// Type the locals a macro replacement declares, keyed by declared name.
7608///
7609/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7610/// serves: the receiver's type is stated inside the replacement itself. A name
7611/// whose declared type does not resolve is kept with no unit, so a call on it
7612/// stays silent instead of falling through to file-scope name lookup.
7613fn macro_replacement_local_receivers(
7614    statements: Node<'_>,
7615    body: &ParsedReplacementBody,
7616    ctx: &ScanCtx<'_>,
7617) -> HashMap<String, Option<CodeUnit>> {
7618    let mut locals = HashMap::default();
7619    let mut stack = vec![statements];
7620    while let Some(current) = stack.pop() {
7621        push_named_children_reversed(current, &mut stack);
7622        if current.kind() != "declaration" {
7623            continue;
7624        }
7625        let Some(type_node) = current
7626            .child_by_field_name("type")
7627            .or_else(|| first_type_child(current))
7628        else {
7629            continue;
7630        };
7631        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7632        let mut cursor = current.walk();
7633        for child in current.named_children(&mut cursor) {
7634            let declarator = if child.kind() == "init_declarator" {
7635                child.child_by_field_name("declarator")
7636            } else {
7637                is_declarator_node(child).then_some(child)
7638            };
7639            let Some(name) =
7640                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7641            else {
7642                continue;
7643            };
7644            locals.insert(name, unit.clone());
7645        }
7646    }
7647    locals
7648}
7649
7650fn macro_replacement_declared_unit(
7651    type_node: Node<'_>,
7652    body: &ParsedReplacementBody,
7653    ctx: &ScanCtx<'_>,
7654) -> Option<CodeUnit> {
7655    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7656    let unit = match ctx
7657        .visibility
7658        .resolve_type_node_result(ctx.file, type_node, &body.source)
7659    {
7660        Ok(Some(unit)) => Some(unit),
7661        Ok(None) => ctx
7662            .visibility
7663            .canonical_type_for_reference(ctx.file, &name)
7664            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7665        Err(_) => None,
7666    }?;
7667    canonical_receiver_unit(&unit, ctx)
7668}
7669
7670/// Whether the queried member is a candidate this file can see.
7671///
7672/// A macro parameter receiver has no type at the definition, so a member call
7673/// on it is at most unproven. Requiring the target to be visible here keeps a
7674/// same-named member of an unrelated translation unit out of the result.
7675fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7676    ctx.visibility
7677        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7678        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7679}
7680
7681fn maybe_record_recovered_relational_template_method_hit(
7682    call: RecoveredRelationalTemplateMemberCall<'_>,
7683    ctx: &mut ScanCtx<'_>,
7684) {
7685    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7686        return;
7687    }
7688    ctx.usages.note_raw_match();
7689    if !ctx
7690        .visibility
7691        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7692        || ctx
7693            .spec
7694            .callable_arity_at(call.member.start_byte())
7695            .is_some_and(|arity| !arity.accepts(call.arity))
7696    {
7697        return;
7698    }
7699    record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7700}
7701
7702fn record_explicit_receiver_method_hit(
7703    receiver: Node<'_>,
7704    member: Node<'_>,
7705    arity: Option<usize>,
7706    ctx: &mut ScanCtx<'_>,
7707) {
7708    match explicit_receiver_target_resolution(receiver, arity, ctx) {
7709        MethodReceiverTargetResolution::Target
7710            if receiver_is_self_like(
7711                receiver,
7712                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7713            ) =>
7714        {
7715            push_self_receiver_hit(member, ctx);
7716        }
7717        MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7718        MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7719        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7720    }
7721}
7722
7723fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7724    if node.kind() != "qualified_identifier" {
7725        return None;
7726    }
7727    let parameter = node
7728        .parent()
7729        .filter(|parent| parent.kind() == "parameter_declaration")?;
7730    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7731    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7732    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7733    // and `(arg)` as an abstract function declarator. Ordinary qualified
7734    // parameter types have named/pointer/reference declarators instead.
7735    if parameter.child_by_field_name("type") != Some(node)
7736        || parameter_declarator.kind() != "abstract_function_declarator"
7737    {
7738        return None;
7739    }
7740    let parameter_list = parameter
7741        .parent()
7742        .filter(|parent| parent.kind() == "parameter_list")?;
7743    if parameter_list.named_child_count() != 1 {
7744        return None;
7745    }
7746    let function_declarator = parameter_list
7747        .parent()
7748        .filter(|parent| parent.kind() == "function_declarator")?;
7749    if function_declarator
7750        .child_by_field_name("declarator")
7751        .is_none_or(|declarator| declarator.kind() != "identifier")
7752        || function_declarator
7753            .parent()
7754            .is_none_or(|parent| parent.kind() != "declaration")
7755    {
7756        return None;
7757    }
7758    node.child_by_field_name("name")
7759}
7760
7761fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7762    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7763        return;
7764    };
7765    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7766        return;
7767    }
7768    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7769        return;
7770    };
7771    ctx.usages.note_raw_match();
7772    let owner_resolution = qualified_owner_components(imported, ctx.source)
7773        .map(|qualified| {
7774            let lexical_scope = match enclosing_lexical_scope_components(
7775                imported,
7776                &ctx.analyzer,
7777                ctx.visibility,
7778                ctx.file,
7779                ctx.source,
7780            ) {
7781                LexicalScopeResolution::Resolved(scope) => scope,
7782                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7783                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7784            };
7785            ctx.visibility.resolve_type_components_lexically(
7786                &ctx.analyzer,
7787                ctx.file,
7788                &qualified.names,
7789                qualified.global,
7790                &lexical_scope,
7791            )
7792        })
7793        .unwrap_or(LexicalTypeResolution::Missing);
7794    let matches_target_owner = matches!(
7795        owner_resolution,
7796        LexicalTypeResolution::Resolved {
7797            ref unit,
7798            ref candidates,
7799            ..
7800        } if same_visible_symbol(unit, target_owner)
7801            || candidates
7802                .iter()
7803                .any(|candidate| same_visible_symbol(candidate, target_owner))
7804    );
7805    if !matches_target_owner {
7806        match owner_resolution {
7807            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7808                push_unproven_hit(imported, ctx);
7809            }
7810            LexicalTypeResolution::Resolved { .. } => {}
7811        }
7812        return;
7813    }
7814    match visible_target_callable_member_resolution(target_owner, ctx) {
7815        VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7816        VisibleTargetCallableMemberResolution::NonCallable => {}
7817        VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7818    }
7819}
7820
7821enum VisibleTargetCallableMemberResolution {
7822    Target,
7823    NonCallable,
7824    Unresolved,
7825}
7826
7827fn visible_target_callable_member_resolution(
7828    owner: &CodeUnit,
7829    ctx: &ScanCtx<'_>,
7830) -> VisibleTargetCallableMemberResolution {
7831    match ctx
7832        .visibility
7833        .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7834    {
7835        VisibleMemberResolution::Callable(candidates)
7836            if candidates.iter().all(|candidate| {
7837                ctx.target_group.contains(candidate)
7838                    || ctx
7839                        .target_group
7840                        .iter()
7841                        .any(|target| same_visible_symbol(candidate, target))
7842            }) =>
7843        {
7844            VisibleTargetCallableMemberResolution::Target
7845        }
7846        VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7847        VisibleMemberResolution::Callable(_)
7848        | VisibleMemberResolution::AmbiguousKind
7849        | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7850    }
7851}
7852
7853fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7854    let name = node_text(node, ctx.source);
7855    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7856    let key = (namespace.clone(), name.to_string());
7857    if let Some(resolved) = ctx
7858        .lexical_free_function_cache
7859        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
7860            cells.borrow().get(&key).copied()
7861        })
7862    {
7863        return resolved;
7864    }
7865    let resolved = ctx
7866        .visibility
7867        .visible_identifier_candidates(ctx.file, name)
7868        .any(|unit| {
7869            unit.is_function()
7870                && type_owner_of(&ctx.analyzer, unit).is_none()
7871                && unit.package_name() == namespace
7872        });
7873    ctx.lexical_free_function_cache
7874        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
7875            cells.borrow_mut().insert(key, resolved)
7876        });
7877    resolved
7878}
7879
7880fn maybe_record_qualified_method_value_hit(
7881    qualified: Node<'_>,
7882    member: Node<'_>,
7883    ctx: &mut ScanCtx<'_>,
7884) {
7885    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7886        return;
7887    }
7888    ctx.usages.note_raw_match();
7889    let resolution =
7890        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7891    match resolution {
7892        LexicalCallableValueResolution::Type(resolved_owner) => {
7893            let Some(owner) = ctx.spec.owner.as_ref() else {
7894                push_unproven_hit(member, ctx);
7895                return;
7896            };
7897            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7898                if same_visible_symbol(&resolved_owner, owner) {
7899                    push_unproven_hit(member, ctx);
7900                }
7901                return;
7902            }
7903            // An explicitly qualified method value remains an external
7904            // reference even when its owner is the enclosing class.
7905            match visible_target_callable_member_resolution(owner, ctx) {
7906                VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7907                VisibleTargetCallableMemberResolution::NonCallable => {}
7908                VisibleTargetCallableMemberResolution::Unresolved => {
7909                    push_unproven_hit(member, ctx);
7910                }
7911            }
7912        }
7913        LexicalCallableValueResolution::FreeFunction(_) => {}
7914        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7915            push_unproven_hit(member, ctx);
7916        }
7917    }
7918}
7919
7920fn qualified_callable_value_resolution(
7921    qualified: Node<'_>,
7922    member_name: &str,
7923    ctx: &ScanCtx<'_>,
7924) -> LexicalCallableValueResolution {
7925    let Some((owner_components, global)) =
7926        qualified_callable_owner_components(qualified, ctx.source)
7927    else {
7928        return LexicalCallableValueResolution::Missing;
7929    };
7930    let lexical_scope = if global {
7931        Vec::new()
7932    } else {
7933        match enclosing_lexical_scope_components(
7934            qualified,
7935            &ctx.analyzer,
7936            ctx.visibility,
7937            ctx.file,
7938            ctx.source,
7939        ) {
7940            LexicalScopeResolution::Resolved(scope) => scope,
7941            LexicalScopeResolution::Ambiguous => {
7942                return LexicalCallableValueResolution::Ambiguous;
7943            }
7944            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7945        }
7946    };
7947    if let Some(target_owner) = ctx.spec.owner.as_ref()
7948        && let LexicalTypeResolution::Resolved { unit, .. } =
7949            resolve_type_components_lexically_at_for_target_with_scope_cache(
7950                qualified,
7951                &owner_components,
7952                global,
7953                &ctx.analyzer,
7954                ctx.visibility,
7955                &ctx.ordinary_type_imports,
7956                ctx.file,
7957                ctx.source,
7958                target_owner,
7959                false,
7960                Some(&ctx.lexical_scope_cache),
7961            )
7962    {
7963        return LexicalCallableValueResolution::Type(unit);
7964    }
7965    ctx.visibility.resolve_callable_value_components_lexically(
7966        &ctx.analyzer,
7967        ctx.file,
7968        &owner_components,
7969        member_name,
7970        global,
7971        &lexical_scope,
7972    )
7973}
7974
7975fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7976    let Some(owner) = ctx.spec.owner.as_ref() else {
7977        return true;
7978    };
7979    if ctx.spec.param_types.is_none() {
7980        return true;
7981    }
7982    let candidates = ctx
7983        .visibility
7984        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7985        .into_iter()
7986        .filter(|unit| unit.is_function())
7987        .cloned()
7988        .collect::<Vec<_>>();
7989    callable_candidates_may_match_target(call, candidates, ctx)
7990}
7991
7992fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7993    let Some(function) = function_definition_name_node(node) else {
7994        return;
7995    };
7996    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7997        return;
7998    }
7999    ctx.usages.note_raw_match();
8000    if !function_definition_signature_matches_target(node, ctx) {
8001        return;
8002    }
8003    if node_inside_target_declaration(function, ctx) {
8004        return;
8005    }
8006    if is_structurally_qualified(function) {
8007        match qualified_owner_resolution(function, ctx) {
8008            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
8009            QualifiedOwnerResolution::NonTarget => {}
8010            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8011        }
8012        return;
8013    }
8014    match definition_name_target_resolution(function, TargetKind::Method, ctx) {
8015        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
8016        DefinitionNameTargetResolution::KnownNonTarget => {
8017            // A method definition for another visible owner is a proven non-match.
8018        }
8019        DefinitionNameTargetResolution::Unresolved
8020            if known_non_target_owner_context(function, ctx) => {}
8021        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8022    }
8023}
8024
8025pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8026    let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
8027    ctx.target_declaration_ranges
8028        .iter()
8029        .any(|range| range.contains(&node_range))
8030}
8031
8032fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
8033    let mut receiver = None;
8034    let mut cursor = node.walk();
8035    for child in node.named_children(&mut cursor) {
8036        if child.kind() == "argument_list" {
8037            continue;
8038        }
8039        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
8040            return receiver.map(|receiver| (receiver, operator));
8041        }
8042        if receiver.is_none() {
8043            receiver = Some(child);
8044        }
8045    }
8046    None
8047}
8048
8049/// Return the name node of a C++ function definition's declarator.
8050pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
8051    if node.kind() != "function_definition" {
8052        return None;
8053    }
8054    node.child_by_field_name("declarator")
8055        .and_then(declarator_name_node)
8056}
8057
8058fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
8059    function_definition_name_node(node)
8060}
8061
8062fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8063    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8064        return true;
8065    };
8066    let Some(candidate) = canonical_callable_at_node(
8067        node,
8068        &ctx.analyzer,
8069        ctx.visibility,
8070        ctx.file,
8071        ctx.source,
8072        ctx.spec.kind,
8073    ) else {
8074        return false;
8075    };
8076    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
8077        return false;
8078    };
8079    if !callable_arity_matches(expected, actual_arity) {
8080        return false;
8081    }
8082    ctx.spec.param_types.as_ref().is_none_or(|expected| {
8083        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
8084            .is_some_and(|actual| actual == *expected)
8085    })
8086}
8087
8088fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8089    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8090        return true;
8091    };
8092    cpp_callable_arity(&ctx.analyzer, candidate)
8093        .is_some_and(|actual| callable_arity_matches(expected, actual))
8094}
8095
8096fn callable_arity_matches(
8097    expected: brokk_bifrost_core::analyzer::model::CallableArity,
8098    actual: brokk_bifrost_core::analyzer::model::CallableArity,
8099) -> bool {
8100    expected.accepts(actual.total())
8101}
8102
8103fn canonical_callable_at_node(
8104    node: Node<'_>,
8105    analyzer: &CppGraphSource<'_>,
8106    visibility: &VisibilityIndex<'_>,
8107    file: &ProjectFile,
8108    source: &str,
8109    kind: TargetKind,
8110) -> Option<CodeUnit> {
8111    let name = callable_name_node(node)?;
8112    let name_range = (name.start_byte(), name.end_byte());
8113    let name_text = node_text(name, source);
8114    for candidate in visibility.named_candidates(file, name_text, kind) {
8115        if candidate.source() != file || !candidate.is_function() {
8116            continue;
8117        }
8118        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
8119            continue;
8120        };
8121        for occurrence in occurrences {
8122            if occurrence
8123                .name_range
8124                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
8125            {
8126                return Some(candidate);
8127            }
8128        }
8129    }
8130    None
8131}
8132
8133fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
8134    let name = if node.kind() == "function_definition" {
8135        function_definition_name_node(node)?
8136    } else {
8137        node.child_by_field_name("declarator")
8138            .and_then(declarator_name_node)?
8139    };
8140    Some(function_terminal_node(name))
8141}
8142
8143fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
8144    name_matches_callable(node_text(function_terminal_node(node), source), expected)
8145}
8146
8147enum DefinitionNameTargetResolution {
8148    Target,
8149    KnownNonTarget,
8150    Unresolved,
8151}
8152
8153fn definition_name_target_resolution(
8154    function: Node<'_>,
8155    target_kind: TargetKind,
8156    ctx: &ScanCtx<'_>,
8157) -> DefinitionNameTargetResolution {
8158    let candidates = definition_name_candidates(function, ctx);
8159    if candidates
8160        .iter()
8161        .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
8162        .any(|name| {
8163            ctx.visibility
8164                .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
8165        })
8166    {
8167        DefinitionNameTargetResolution::Target
8168    } else if candidates.iter().any(|name| {
8169        ctx.visibility
8170            .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
8171    }) {
8172        DefinitionNameTargetResolution::KnownNonTarget
8173    } else {
8174        DefinitionNameTargetResolution::Unresolved
8175    }
8176}
8177
8178fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
8179    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
8180    if raw.is_empty() {
8181        return Vec::new();
8182    }
8183    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
8184        return vec![raw];
8185    };
8186    if !raw.contains("::") {
8187        return vec![format!("{namespace}::{raw}")];
8188    }
8189    // fqname-M4: peeks at the raw first `::`-split token, including the empty
8190    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
8191    // shape as rust's `rust_reference_looks_external`); the shared structured
8192    // splitter filters empty segments, which would shift "which token is
8193    // first" for that one lead-`::` shape and is not proven equivalent here.
8194    if raw
8195        .split("::")
8196        .next()
8197        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
8198    {
8199        vec![format!("{namespace}::{raw}"), raw]
8200    } else {
8201        vec![raw]
8202    }
8203}
8204
8205fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8206    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
8207}
8208
8209/// Whether a name leaf the field scans reached sits in a value position.
8210///
8211/// Every kind those scans admit already names a value except `type_identifier`.
8212/// tree-sitter spells a non-type template argument with type syntax, so the
8213/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
8214/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
8215/// Admit that leaf only where no visible type explains the spelling, which is
8216/// the order forward navigation applies before it reads the leaf in the value
8217/// namespace (#2556). Every other `type_identifier` is a type reference and
8218/// belongs to the type scan.
8219fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8220    node.kind() != "type_identifier"
8221        || (is_type_shaped_template_argument_name(node)
8222            && ctx
8223                .visibility
8224                .resolve_type(ctx.file, node_text(node, ctx.source))
8225                .is_none())
8226}
8227
8228fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8229    if is_c_offsetof_member_node(node) {
8230        return;
8231    }
8232    if matches!(node.kind(), "identifier" | "field_identifier")
8233        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8234            .is_some()
8235    {
8236        return;
8237    }
8238    if !matches!(
8239        node.kind(),
8240        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
8241    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8242        || !scan_leaf_is_value_position(node, ctx)
8243        || is_declaration_name(node)
8244        || is_member_field_own_declarator(node, ctx)
8245        || is_selected_field_expression_member_descendant(node)
8246        || is_nested_in_qualified_identifier(node)
8247    {
8248        return;
8249    }
8250    ctx.usages.note_raw_match();
8251    if global_field_resolves_to_target(node, ctx) {
8252        push_hit(node, ctx);
8253    } else if global_field_is_known_non_target(node, ctx) {
8254    } else {
8255        push_unproven_hit(node, ctx);
8256    }
8257}
8258
8259/// Whether `node` belongs to the selected-member side of any enclosing field
8260/// expression. A reference may be nested arbitrarily inside the receiver side
8261/// (for example, an argument to a call-built fluent receiver), so direct child
8262/// equality is insufficient: classify each ancestor by structured subtree
8263/// containment instead.
8264fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
8265    let candidate = node;
8266    while let Some(parent) = node.parent() {
8267        if parent.kind() == "field_expression" {
8268            if let Some(field) = parent.child_by_field_name("field")
8269                && node_is_within(field, candidate)
8270            {
8271                // The `template` disambiguator in `receiver.template f<Arg>()`
8272                // wraps the selected member in a `dependent_name` whose single
8273                // named child is the `template_*` node the plain spelling puts
8274                // directly under `field`. Unwrap it so both spellings reach the
8275                // same name/arguments split (#2196).
8276                let selected = match field.kind() {
8277                    "dependent_name" => field.named_child(0).unwrap_or(field),
8278                    _ => field,
8279                };
8280                let selected_name = match selected.kind() {
8281                    "template_method" | "template_function" | "template_type" => {
8282                        selected.child_by_field_name("name").unwrap_or(selected)
8283                    }
8284                    _ => selected,
8285                };
8286                if node_is_within(selected_name, candidate) {
8287                    return true;
8288                }
8289                // A template argument is structurally inside the field subtree,
8290                // but it is an independent reference rather than the selected
8291                // member name.
8292                node = parent;
8293                continue;
8294            }
8295            let receiver = parent
8296                .child_by_field_name("argument")
8297                .or_else(|| parent.child_by_field_name("object"))
8298                .or_else(|| parent.named_child(0));
8299            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8300                // Unknown grammar shape inside a field expression: fail closed
8301                // rather than treating it as a receiver reference.
8302                return true;
8303            }
8304        }
8305        node = parent;
8306    }
8307    false
8308}
8309
8310fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8311    let text = node_text(node, ctx.source);
8312    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8313        return false;
8314    }
8315    if text.contains("::") {
8316        return ctx.visibility.contains_named_symbol(
8317            ctx.file,
8318            text,
8319            TargetKind::GlobalField,
8320            &ctx.spec.target,
8321        );
8322    }
8323    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8324        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8325    {
8326        return ctx.visibility.contains_named_symbol(
8327            ctx.file,
8328            text,
8329            TargetKind::GlobalField,
8330            &ctx.spec.target,
8331        );
8332    }
8333    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8334        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8335            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8336                brokk_bifrost_core::analyzer::Language::Cpp,
8337                &namespace,
8338            ) == indexed_scope
8339        })
8340    {
8341        return ctx.visibility.contains_named_symbol(
8342            ctx.file,
8343            text,
8344            TargetKind::GlobalField,
8345            &ctx.spec.target,
8346        );
8347    }
8348    bare_global_field_uniquely_resolves_to_target(text, ctx)
8349}
8350
8351fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8352    let mut matched_target = false;
8353    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8354        if !has_persisted_global_field_identity(unit)
8355            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8356        {
8357            continue;
8358        }
8359        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8360            continue;
8361        }
8362        if ctx.global_field_internal_linkage_cache.read(
8363            &ctx.visibility.reads.memo_attribution(),
8364            |cells| {
8365                same_visible_global_field_symbol(
8366                    &ctx.analyzer,
8367                    &mut cells.borrow_mut(),
8368                    unit,
8369                    &ctx.spec.target,
8370                )
8371            },
8372        ) {
8373            matched_target = true;
8374        } else {
8375            return false;
8376        }
8377    }
8378    matched_target
8379}
8380
8381pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8382    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8383    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8384    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8385    // analyzer for every same-named candidate's parent.
8386    unit.is_field() && !unit.short_name().contains('.')
8387}
8388
8389fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8390    let text = node_text(node, ctx.source);
8391    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8392        return true;
8393    }
8394    if text.contains("::") {
8395        return ctx.visibility.resolve_known_non_target(
8396            ctx.file,
8397            text,
8398            TargetKind::GlobalField,
8399            &ctx.spec.target,
8400        );
8401    }
8402    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8403        return false;
8404    };
8405    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8406        && ctx
8407            .visibility
8408            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8409            .any(|unit| {
8410                has_persisted_global_field_identity(unit)
8411                    && unit.identifier() == ctx.spec.member_name
8412                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8413                    && !ctx.global_field_internal_linkage_cache.read(
8414                        &ctx.visibility.reads.memo_attribution(),
8415                        |cells| {
8416                            same_visible_global_field_symbol(
8417                                &ctx.analyzer,
8418                                &mut cells.borrow_mut(),
8419                                unit,
8420                                &ctx.spec.target,
8421                            )
8422                        },
8423                    )
8424            })
8425}
8426
8427fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8428    if is_c_offsetof_member_node(node) {
8429        maybe_record_c_offsetof_field_hit(node, ctx);
8430        return;
8431    }
8432    if node.kind() == "field_expression" {
8433        let Some(field) = node.child_by_field_name("field") else {
8434            return;
8435        };
8436        if node_text(field, ctx.source) != ctx.spec.member_name {
8437            return;
8438        }
8439        ctx.usages.note_raw_match();
8440        if !ctx.visibility.declaration_guards_compatible_at_reference(
8441            &ctx.analyzer,
8442            ctx.file,
8443            &ctx.spec.target,
8444            field,
8445        ) {
8446            return;
8447        }
8448        let receiver = node
8449            .child_by_field_name("argument")
8450            .or_else(|| node.child_by_field_name("object"));
8451        let receiver_resolution =
8452            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8453        match receiver_resolution {
8454            Some(MethodReceiverTargetResolution::Target)
8455                if ctx.visibility.declaration_visible_at_reference(
8456                    &ctx.analyzer,
8457                    ctx.file,
8458                    &ctx.spec.target,
8459                    field,
8460                ) || (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
8461                    && ctx.spec.target.source() == ctx.file
8462                    && ctx
8463                        .visibility
8464                        .external_type_candidate_guard_compatible_in_context(
8465                            &ctx.analyzer,
8466                            ctx.file,
8467                            &ctx.spec.target,
8468                            field,
8469                        )) =>
8470            {
8471                // The receiver proves the C++ member owner, including fields
8472                // recovered without callable activation facts. Its guard still
8473                // needs implication and stability, even in the same file.
8474                push_hit(field, ctx)
8475            }
8476            Some(
8477                MethodReceiverTargetResolution::Target | MethodReceiverTargetResolution::Missing,
8478            )
8479            | None => push_unproven_hit(field, ctx),
8480            Some(
8481                MethodReceiverTargetResolution::NonTarget
8482                | MethodReceiverTargetResolution::Ambiguous,
8483            ) => {}
8484        }
8485        return;
8486    }
8487
8488    if matches!(node.kind(), "identifier" | "field_identifier")
8489        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8490        && let Some(designator_owner) =
8491            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8492    {
8493        ctx.usages.note_raw_match();
8494        match designator_owner {
8495            DesignatedInitializerOwner::Resolved(owner)
8496                if ctx
8497                    .spec
8498                    .owner
8499                    .as_ref()
8500                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8501            {
8502                push_hit(node, ctx);
8503            }
8504            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8505            DesignatedInitializerOwner::Resolved(_) => {}
8506        }
8507        return;
8508    }
8509
8510    let qualified_member_name_matches =
8511        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8512            && cpp_name_component_nodes(node)
8513                .and_then(|components| components.last().copied())
8514                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8515    if !matches!(
8516        node.kind(),
8517        "identifier"
8518            | "field_identifier"
8519            | "qualified_identifier"
8520            | "scoped_identifier"
8521            | "type_identifier"
8522    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8523        && !qualified_member_name_matches)
8524        || !scan_leaf_is_value_position(node, ctx)
8525        || is_declaration_name(node)
8526        || is_member_field_own_declarator(node, ctx)
8527        || is_selected_field_expression_member_descendant(node)
8528        || is_nested_in_qualified_identifier(node)
8529    {
8530        return;
8531    }
8532    ctx.usages.note_raw_match();
8533    if is_structurally_qualified(node) {
8534        match qualified_owner_resolution(node, ctx) {
8535            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8536            QualifiedOwnerResolution::NonTarget => {}
8537            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8538        }
8539        return;
8540    }
8541    let text = node_text(node, ctx.source);
8542    if ctx.local_shadows.is_shadowed(text) {
8543        return;
8544    }
8545    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8546        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8547    let owner_context = structured_owner_context_resolution(node, ctx);
8548    if matches!(
8549        owner_context,
8550        StructuredOwnerContextResolution::SelfTarget
8551            | StructuredOwnerContextResolution::InheritedTarget
8552    ) || unscoped_enum_match
8553    {
8554        push_hit(node, ctx);
8555    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8556        .then_some(ctx.spec.owner.as_ref())
8557        .flatten()
8558    {
8559        let resolution = match resolve_active_using_enum_member(node, ctx) {
8560            ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8561            ActiveUsingEnumMemberResolution::Class(resolution) => {
8562                if direct_class_member_shadows(node, ctx) {
8563                    return;
8564                }
8565                resolution
8566            }
8567            ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8568                if let Some(owner) = structured_enclosing_owner(node, ctx) {
8569                    if direct_class_member_shadows(node, ctx) {
8570                        return;
8571                    }
8572                    let complete_same_file_leaf = owner.source() == ctx.file
8573                        && ctx
8574                            .analyzer
8575                            .type_hierarchy_provider()
8576                            .is_some_and(|hierarchy| {
8577                                hierarchy
8578                                    .get_direct_ancestors(&owner)
8579                                    .definite()
8580                                    .to_vec()
8581                                    .is_empty()
8582                            });
8583                    if !complete_same_file_leaf {
8584                        push_unproven_hit(node, ctx);
8585                        return;
8586                    }
8587                }
8588                match owner_context {
8589                    StructuredOwnerContextResolution::SelfTarget
8590                    | StructuredOwnerContextResolution::InheritedTarget
8591                    | StructuredOwnerContextResolution::NonTarget => return,
8592                    StructuredOwnerContextResolution::Ambiguous => {
8593                        push_unproven_hit(node, ctx);
8594                        return;
8595                    }
8596                    StructuredOwnerContextResolution::Missing => {}
8597                }
8598                if namespace_value_shadows(node, ctx) {
8599                    return;
8600                }
8601                resolution
8602            }
8603            ActiveUsingEnumMemberResolution::Missing => {
8604                if direct_class_member_shadows(node, ctx)
8605                    || (structured_enclosing_owner(node, ctx).is_none()
8606                        && namespace_value_shadows(node, ctx))
8607                {
8608                    return;
8609                }
8610                UsingEnumMemberResolution::Missing
8611            }
8612        };
8613        match resolution {
8614            UsingEnumMemberResolution::Resolved { owner, member }
8615                if same_visible_symbol(&owner, target_owner)
8616                    && same_visible_symbol(&member, &ctx.spec.target) =>
8617            {
8618                push_hit(node, ctx);
8619            }
8620            UsingEnumMemberResolution::Resolved { .. } => {}
8621            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8622                push_unproven_hit(node, ctx)
8623            }
8624        }
8625    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8626        push_unproven_hit(node, ctx);
8627    }
8628}
8629
8630fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8631    if node_text(node, ctx.source) != ctx.spec.member_name {
8632        return;
8633    }
8634    ctx.usages.note_raw_match();
8635    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8636        push_unproven_hit(node, ctx);
8637        return;
8638    };
8639    let owner = match resolve_type_node_lexically(
8640        type_reference,
8641        &ctx.analyzer,
8642        ctx.visibility,
8643        &ctx.ordinary_type_imports,
8644        ctx.file,
8645        ctx.source,
8646    ) {
8647        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8648        LexicalTypeResolution::Resolved { .. }
8649        | LexicalTypeResolution::Ambiguous
8650        | LexicalTypeResolution::Missing => {
8651            push_unproven_hit(member, ctx);
8652            return;
8653        }
8654    };
8655    let candidates = ctx
8656        .visibility
8657        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8658        .into_iter()
8659        .filter(|candidate| candidate.is_field())
8660        .collect::<Vec<_>>();
8661    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8662        push_hit(member, ctx);
8663    } else if candidates.len() > 1 {
8664        push_unproven_hit(member, ctx);
8665    }
8666}
8667
8668enum ActiveUsingEnumMemberResolution {
8669    Block(UsingEnumMemberResolution),
8670    Class(UsingEnumMemberResolution),
8671    Namespace(UsingEnumMemberResolution),
8672    Missing,
8673}
8674
8675fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8676    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8677        ctx.visibility
8678            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8679            .into_iter()
8680            .next()
8681            .is_some()
8682    })
8683}
8684
8685fn resolve_active_using_enum_member(
8686    node: Node<'_>,
8687    ctx: &ScanCtx<'_>,
8688) -> ActiveUsingEnumMemberResolution {
8689    let block =
8690        ctx.using_enum_owners
8691            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8692    if !matches!(block, UsingEnumMemberResolution::Missing) {
8693        return ActiveUsingEnumMemberResolution::Block(block);
8694    }
8695    let class = structured_enclosing_owner(node, ctx);
8696    let namespace = enclosing_namespace_components(node, ctx.source);
8697    match ctx.semantic_using_enum_owners.resolve_member(
8698        ctx.visibility,
8699        ctx.file,
8700        class.as_ref(),
8701        &namespace,
8702        node.start_byte(),
8703        &ctx.spec.member_name,
8704    ) {
8705        SemanticUsingEnumMemberResolution::Class(resolution) => {
8706            ActiveUsingEnumMemberResolution::Class(resolution)
8707        }
8708        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8709            ActiveUsingEnumMemberResolution::Namespace(resolution)
8710        }
8711        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8712    }
8713}
8714
8715fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8716    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8717    !matches!(
8718        resolve_namespace_value(
8719            &ctx.analyzer,
8720            ctx.visibility,
8721            ctx.file,
8722            &namespace,
8723            &ctx.spec.member_name,
8724            node.start_byte(),
8725        ),
8726        NamespaceValueResolution::Missing
8727    )
8728}
8729
8730/// Whether `node` is a component of an enclosing qualified identifier's own
8731/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8732/// reference surfaced for it.
8733///
8734/// A qualified identifier owns only that path. Everything else beneath it is an
8735/// independent reference that its own target scanner must resolve:
8736///
8737/// - `Owner::Template<argument>` holds each template argument outside the path.
8738/// - Error recovery can hang a complete member initializer off an `ERROR` child
8739///   of a synthetic qualified identifier, where no structured path exists.
8740/// - The grammar accepts a `pointer_type_declarator` as a qualified
8741///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8742///   folds a whole parameter list under one `qualified_identifier`. Parameter
8743///   default values there are ordinary value expressions (#2548).
8744///
8745/// Follow the structured `scope`/`name` links rather than every ancestor, which
8746/// covers all three cases with one rule.
8747/// Whether `node` lies on the scope/name path of an enclosing
8748/// `qualified_identifier`, which owns the complete name. The grammar nests
8749/// `::A::b` and `n::A::b` as a qualified suffix `A::b`; resolving that suffix
8750/// on its own would drop the leading `::` or `n::` qualifier.
8751fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8752    let mut current = node;
8753    while let Some(parent) = current.parent() {
8754        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8755            parent
8756                .child_by_field_name(field)
8757                .is_some_and(|component| same_node(component, current))
8758        });
8759        if !on_name_path {
8760            return false;
8761        }
8762        if parent.kind() == "qualified_identifier" {
8763            return true;
8764        }
8765        current = parent;
8766    }
8767    false
8768}
8769
8770fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8771    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8772}
8773
8774/// What a receiver spelled as a qualified name denotes.
8775///
8776/// `ns::value.run()` reads the member off the declared type of the variable
8777/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8778/// Forward navigation and the inverse walk both ask this before typing such a
8779/// receiver, so the two directions accept the same receiver spellings (#2547).
8780pub enum QualifiedReceiverBase {
8781    /// Every visible declaration of the one variable the path names.
8782    Variable(Vec<CodeUnit>),
8783    /// The type the path names when it names no variable.
8784    Type(Option<CodeUnit>),
8785    /// The path names more than one variable; typing either would be a guess.
8786    Ambiguous,
8787}
8788
8789pub fn qualified_receiver_base(
8790    visibility: &VisibilityIndex<'_>,
8791    file: &ProjectFile,
8792    receiver: Node<'_>,
8793    source: &str,
8794) -> QualifiedReceiverBase {
8795    let reference = node_text(receiver, source);
8796    let fields = visibility
8797        .named_candidates(file, reference, TargetKind::GlobalField)
8798        .into_iter()
8799        .filter(has_persisted_global_field_identity)
8800        .collect::<Vec<_>>();
8801    let Some(first) = fields.first() else {
8802        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8803    };
8804    if fields
8805        .iter()
8806        .skip(1)
8807        .any(|field| !same_visible_symbol(first, field))
8808    {
8809        return QualifiedReceiverBase::Ambiguous;
8810    }
8811    QualifiedReceiverBase::Variable(fields)
8812}
8813
8814fn receiver_type_units_with_budget(
8815    node: Node<'_>,
8816    source: &str,
8817    ctx: &ScanCtx<'_>,
8818    remaining_call_depth: usize,
8819) -> Vec<CodeUnit> {
8820    let mut current = node;
8821    let mut member_chain = Vec::new();
8822    let mut base_units = loop {
8823        match current.kind() {
8824            "field_expression" => {
8825                let Some(member) = current.child_by_field_name("field") else {
8826                    return Vec::new();
8827                };
8828                let Some(receiver) = current
8829                    .child_by_field_name("argument")
8830                    .or_else(|| current.child_by_field_name("object"))
8831                    .or_else(|| current.named_child(0))
8832                else {
8833                    return Vec::new();
8834                };
8835                member_chain.push(node_text(member, source));
8836                current = receiver;
8837            }
8838            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8839                let Some(inner) = current
8840                    .child_by_field_name("argument")
8841                    .or_else(|| current.named_child(0))
8842                else {
8843                    return Vec::new();
8844                };
8845                current = inner;
8846            }
8847            // Tree-sitter uses `field_identifier` for an unqualified member
8848            // field when it appears as the base of another field expression
8849            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8850            // same structured binding and enclosing-owner paths as an
8851            // ordinary identifier; falling through to `resolve_type` would
8852            // treat the field name as a type and lose the receiver identity.
8853            "identifier" | "field_identifier" => {
8854                let name = node_text(current, source);
8855                // Function-like macro locals are visible in the replacement
8856                // range after substitution and shadow caller bindings. The
8857                // receiver scan has no root field, so recover the source tree
8858                // root before asking the visibility index for that binding.
8859                let mut root = current;
8860                while let Some(parent) = ctx.ancestry.parent(root) {
8861                    root = parent;
8862                }
8863                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8864                    ctx.file,
8865                    root,
8866                    source,
8867                    current.start_byte(),
8868                    current.end_byte(),
8869                ) && binding.name == name
8870                {
8871                    let normalized = normalize_cpp_type_name(&binding.type_name);
8872                    let unit = binding
8873                        .proven_unit
8874                        .clone()
8875                        .or_else(|| {
8876                            binding.type_node.and_then(|type_node| {
8877                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8878                            })
8879                        })
8880                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8881                    // Keep the macro declaration's shadowing boundary even
8882                    // when its type is unavailable: an unresolved macro local
8883                    // cannot be reinterpreted as a static type or outer value.
8884                    break unit.into_iter().collect();
8885                }
8886                let local = ctx.bindings.resolve_symbol(name);
8887                if let Some(bindings) = local.as_precise() {
8888                    break receiver_units_from_bindings(current, bindings, ctx);
8889                }
8890                if ctx.bindings.is_shadowed(name) {
8891                    return Vec::new();
8892                }
8893                let owner = structured_enclosing_owner(current, ctx)
8894                    .filter(CodeUnit::is_class)
8895                    .or_else(|| {
8896                        enclosing_context(current, ctx)
8897                            .owner
8898                            .filter(CodeUnit::is_class)
8899                    });
8900                if let Some(owner) = owner {
8901                    // The enclosing class is only the search root: an implicit
8902                    // member-field receiver can be declared on any base, so the
8903                    // declaring owner comes from the same hierarchy walk the
8904                    // member chain below uses, not from an exact-parent match.
8905                    let declaring_owner = match resolve_declaring_member_owner(
8906                        &ctx.analyzer,
8907                        ctx.visibility,
8908                        ctx.file,
8909                        &owner,
8910                        name,
8911                    ) {
8912                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8913                        EnclosingMemberOwnerResolution::Missing => None,
8914                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8915                    };
8916                    if let Some(declaring_owner) = declaring_owner {
8917                        let implicit_fields = ctx
8918                            .visibility
8919                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8920                            .into_iter()
8921                            .filter(|unit| unit.is_field())
8922                            .collect::<Vec<_>>();
8923                        if !implicit_fields.is_empty() {
8924                            break receiver_units_from_declared_fields(
8925                                implicit_fields,
8926                                current,
8927                                ctx,
8928                            );
8929                        }
8930                    }
8931                }
8932                let global_fields = ctx
8933                    .visibility
8934                    .visible_identifier_candidates(ctx.file, name)
8935                    .filter(|unit| {
8936                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8937                    })
8938                    .collect::<Vec<_>>();
8939                if global_fields.is_empty() {
8940                    break ctx
8941                        .visibility
8942                        .resolve_type(ctx.file, name)
8943                        .into_iter()
8944                        .collect();
8945                }
8946                if let Some(first) = global_fields.first()
8947                    && global_fields.iter().skip(1).any(|field| {
8948                        !ctx.global_field_internal_linkage_cache.read(
8949                            &ctx.visibility.reads.memo_attribution(),
8950                            |cells| {
8951                                same_visible_global_field_symbol(
8952                                    &ctx.analyzer,
8953                                    &mut cells.borrow_mut(),
8954                                    first,
8955                                    field,
8956                                )
8957                            },
8958                        )
8959                    })
8960                {
8961                    return Vec::new();
8962                }
8963                break receiver_units_from_declared_fields(global_fields, current, ctx);
8964            }
8965            "call_expression" | "new_expression" => {
8966                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8967                    .and_then(|binding| binding.unit)
8968                    .into_iter()
8969                    .collect();
8970            }
8971            // `((T*)p)->member` is typed by what the cast spells, not by the
8972            // operand it converts (#2981). Pointer depth does not change which
8973            // type declares the member, so only the descriptor's type matters.
8974            "cast_expression" => {
8975                let Some(descriptor) = current.child_by_field_name("type") else {
8976                    return Vec::new();
8977                };
8978                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8979                    return Vec::new();
8980                };
8981                break unit.into_iter().collect();
8982            }
8983            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8984                let name = node_text(current, source);
8985                let local = ctx.bindings.resolve_symbol(name);
8986                if let Some(bindings) = local.as_precise() {
8987                    break receiver_units_from_bindings(current, bindings, ctx);
8988                }
8989                return Vec::new();
8990            }
8991            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8992            "qualified_identifier" | "scoped_identifier" => {
8993                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8994                    QualifiedReceiverBase::Variable(fields) => {
8995                        break receiver_units_from_declared_fields(
8996                            fields.iter().collect(),
8997                            current,
8998                            ctx,
8999                        );
9000                    }
9001                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
9002                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
9003                }
9004            }
9005            _ => {
9006                break ctx
9007                    .visibility
9008                    .resolve_type(ctx.file, node_text(current, source))
9009                    .into_iter()
9010                    .collect();
9011            }
9012        }
9013    };
9014
9015    base_units = canonical_receiver_units(base_units, ctx);
9016    if base_units.is_empty() {
9017        return Vec::new();
9018    }
9019
9020    while let Some(member_name) = member_chain.pop() {
9021        let mut next_units = Vec::new();
9022        for owner in &base_units {
9023            let declaring_owner = match resolve_declaring_member_owner(
9024                &ctx.analyzer,
9025                ctx.visibility,
9026                ctx.file,
9027                owner,
9028                member_name,
9029            ) {
9030                EnclosingMemberOwnerResolution::Owner(owner) => owner,
9031                EnclosingMemberOwnerResolution::Missing => continue,
9032                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
9033            };
9034            let fields = ctx.visibility.visible_members_for_owner_name(
9035                ctx.file,
9036                &declaring_owner,
9037                member_name,
9038            );
9039            for field in fields.into_iter().filter(|unit| unit.is_field()) {
9040                let Some(unit) =
9041                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9042                        .and_then(|binding| binding.unit)
9043                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
9044                else {
9045                    continue;
9046                };
9047                if !next_units
9048                    .iter()
9049                    .any(|existing| same_visible_symbol(existing, &unit))
9050                {
9051                    next_units.push(unit);
9052                }
9053            }
9054        }
9055        if next_units.is_empty() {
9056            return Vec::new();
9057        }
9058        base_units = unanimous_receiver_units(next_units);
9059        if base_units.is_empty() {
9060            return Vec::new();
9061        }
9062    }
9063    base_units
9064}
9065
9066fn receiver_units_from_bindings(
9067    node: Node<'_>,
9068    bindings: &HashSet<CppScanBinding>,
9069    ctx: &ScanCtx<'_>,
9070) -> Vec<CodeUnit> {
9071    let mut units = Vec::new();
9072    for binding in bindings {
9073        let raw_unit = if let Some(unit) = &binding.unit {
9074            unit.clone()
9075        } else {
9076            let Some(type_name) = binding.type_name.as_deref() else {
9077                return Vec::new();
9078            };
9079            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
9080                return Vec::new();
9081            };
9082            unit
9083        };
9084        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
9085            // Type aliases are represented as class units. When an alias has
9086            // a dependent target, canonicalization deliberately preserves
9087            // that alias rather than inventing a concrete class. Give the
9088            // existing target-guided structured recovery a chance to prove
9089            // the queried owner before accepting that unresolved identity.
9090            if same_visible_symbol(&unit, &raw_unit)
9091                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
9092            {
9093                units.push(recovered);
9094                continue;
9095            }
9096            units.push(unit);
9097            continue;
9098        }
9099        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
9100            units.push(unit);
9101            continue;
9102        }
9103        return Vec::new();
9104    }
9105    unanimous_receiver_units(units)
9106}
9107
9108/// Resolve a using-alias receiver from its declaration's structured RHS when
9109/// the alias target index cannot cross a malformed namespace-sentinel node.
9110/// The inverse target owner supplies only the exact class identity to prove;
9111/// lexical AST resolution still decides whether the alias denotes that class.
9112fn recovered_receiver_alias_target(
9113    reference: Node<'_>,
9114    alias: &CodeUnit,
9115    ctx: &ScanCtx<'_>,
9116) -> Option<CodeUnit> {
9117    if !ctx
9118        .analyzer
9119        .type_alias_provider()
9120        .is_some_and(|provider| provider.is_type_alias(alias))
9121    {
9122        return None;
9123    }
9124    let target = ctx.spec.owner.as_ref()?.clone();
9125    if !target.is_class() || alias.source() != ctx.file {
9126        return None;
9127    }
9128    let range = ctx
9129        .analyzer
9130        .ranges(alias)
9131        .into_iter()
9132        .find(|range| range.start_byte < range.end_byte)?;
9133    let mut node =
9134        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9135    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
9136        node = ctx.ancestry.parent(node)?;
9137    }
9138    let type_descriptor = node.child_by_field_name("type")?;
9139    let type_node = receiver_type_node_base(type_descriptor);
9140    let resolution = resolve_type_node_lexically_for_target(
9141        type_node,
9142        &ctx.analyzer,
9143        ctx.visibility,
9144        &ctx.ordinary_type_imports,
9145        ctx.file,
9146        ctx.source,
9147        &target,
9148        Some(&ctx.lexical_scope_cache),
9149        ctx.recovered_sentinel_scope(type_node).as_deref(),
9150    );
9151    if let LexicalTypeResolution::Resolved {
9152        unit, candidates, ..
9153    } = resolution
9154        && (same_visible_symbol(&unit, &target)
9155            || candidates
9156                .iter()
9157                .any(|candidate| same_visible_symbol(candidate, &target)))
9158    {
9159        return Some(target);
9160    }
9161    let (components, global) = type_reference_components(type_node, ctx.source)?;
9162    if !global
9163        && components.len() == 2
9164        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
9165        && dependent_nested_alias_concrete_target(
9166            &ctx.analyzer,
9167            ctx.visibility,
9168            ctx.file,
9169            alias,
9170            &components[1],
9171            type_node,
9172        )
9173        .is_some_and(|unit| same_visible_symbol(&unit, &target))
9174    {
9175        return Some(target);
9176    }
9177    let scope = ctx
9178        .recovered_sentinel_scope(type_node)
9179        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9180    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
9181    let visible = ctx.visibility.external_type_candidate_visible_in_context(
9182        &ctx.analyzer,
9183        ctx.file,
9184        &target,
9185        type_node,
9186    );
9187    (path_matches && visible).then_some(target)
9188}
9189
9190/// The one concrete class that `alias`, spelled `Param::nested_name` through an
9191/// active template type parameter `Param`, can name: every other visible
9192/// class-owned alias named `nested_name` must reach the same indexed class
9193/// (#2250). `type_node` is the alias's target type node, whose guard context
9194/// each candidate must be compatible with. Competing concrete targets answer
9195/// `None`.
9196pub fn dependent_nested_alias_concrete_target(
9197    analyzer: &CppGraphSource<'_>,
9198    visibility: &VisibilityIndex<'_>,
9199    file: &ProjectFile,
9200    alias: &CodeUnit,
9201    nested_name: &str,
9202    type_node: Node<'_>,
9203) -> Option<CodeUnit> {
9204    let alias_provider = analyzer.type_alias_provider()?;
9205    let concrete = visibility
9206        .visible_identifier_candidates(file, nested_name)
9207        .filter(|candidate| {
9208            alias_provider.is_type_alias(candidate)
9209                && !same_visible_symbol(candidate, alias)
9210                && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
9211                && visibility.is_physically_visible(file, candidate)
9212                && visibility.external_type_candidate_guard_compatible_in_context(
9213                    analyzer, file, candidate, type_node,
9214                )
9215        })
9216        .filter_map(|candidate| {
9217            let canonical =
9218                visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
9219            // Another dependent alias can have the same nested name but
9220            // still canonicalize only to itself. It supplies no concrete
9221            // receiver identity and therefore cannot compete with an
9222            // alias that reaches an indexed class.
9223            (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
9224        })
9225        .collect::<Vec<_>>();
9226    match unanimous_receiver_units(concrete).as_slice() {
9227        [unit] => Some(unit.clone()),
9228        _ => None,
9229    }
9230}
9231
9232fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9233    let normalized = normalize_cpp_type_name(type_name);
9234    if normalized.is_empty() {
9235        return None;
9236    }
9237
9238    // A function-local alias is intentionally absent from the visibility
9239    // index. Recover its RHS from the structured alias declaration before
9240    // trying file-visible type lookup; this keeps the alias's lexical shadow
9241    // boundary intact.
9242    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
9243        match resolve_receiver_type_node(alias_type, ctx) {
9244            Ok(Some(unit)) => return Some(unit),
9245            Err(_) => return None,
9246            Ok(None) => {}
9247        }
9248    }
9249
9250    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
9251        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
9252        LexicalTypeResolution::Ambiguous => return None,
9253        LexicalTypeResolution::Missing => {}
9254    }
9255    let candidates = ctx
9256        .visibility
9257        .type_name_candidates(ctx.file, &normalized)
9258        .into_iter()
9259        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
9260        .collect();
9261    unanimous_receiver_units(candidates).into_iter().next()
9262}
9263
9264/// The receiver type a type node names: the visible indexed type it resolves
9265/// to, else the type its components name in the enclosing lexical scope.
9266///
9267/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
9268/// is unwrapped to its type specifier first, so every caller that has a spelled
9269/// type reads it the same way. `Err` reports a template whose arguments could
9270/// not be resolved; that is a structured failure, not an absent type.
9271fn resolve_receiver_type_node(
9272    type_node: Node<'_>,
9273    ctx: &ScanCtx<'_>,
9274) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
9275    let type_node = receiver_type_node_base(type_node);
9276    if let Some(local) = function_local_type_for_node(type_node, ctx) {
9277        return Ok(local);
9278    }
9279    if let Some(unit) = ctx
9280        .visibility
9281        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
9282    {
9283        return Ok(Some(unit));
9284    }
9285    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
9286}
9287
9288fn resolve_receiver_type_node_lexically(
9289    type_node: Node<'_>,
9290    ctx: &ScanCtx<'_>,
9291) -> Option<CodeUnit> {
9292    let type_node = receiver_type_node_base(type_node);
9293    let components = cpp_type_name_components(type_node, ctx.source)?;
9294    let lexical_scope = match enclosing_lexical_scope_components(
9295        type_node,
9296        &ctx.analyzer,
9297        ctx.visibility,
9298        ctx.file,
9299        ctx.source,
9300    ) {
9301        LexicalScopeResolution::Resolved(scope) => scope,
9302        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
9303    };
9304    match ctx.visibility.resolve_type_components_lexically(
9305        &ctx.analyzer,
9306        ctx.file,
9307        &components,
9308        is_globally_qualified_cpp_name(type_node),
9309        &lexical_scope,
9310    ) {
9311        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
9312        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9313    }
9314}
9315
9316fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
9317    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
9318        let Some(inner) = node.child_by_field_name("type").or_else(|| {
9319            if node.kind() == "dependent_type" {
9320                node.named_child(0)
9321            } else {
9322                None
9323            }
9324        }) else {
9325            break;
9326        };
9327        node = inner;
9328    }
9329    node
9330}
9331
9332fn resolve_receiver_type_name_lexically(
9333    node: Node<'_>,
9334    normalized: &str,
9335    ctx: &ScanCtx<'_>,
9336) -> LexicalTypeResolution {
9337    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9338        brokk_bifrost_core::analyzer::Language::Cpp,
9339        normalized,
9340    );
9341    if components.is_empty() {
9342        return LexicalTypeResolution::Missing;
9343    }
9344    let lexical_scope = match enclosing_lexical_scope_components(
9345        node,
9346        &ctx.analyzer,
9347        ctx.visibility,
9348        ctx.file,
9349        ctx.source,
9350    ) {
9351        LexicalScopeResolution::Resolved(scope) => scope,
9352        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9353        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9354    };
9355    ctx.visibility.resolve_type_components_lexically(
9356        &ctx.analyzer,
9357        ctx.file,
9358        &components,
9359        normalized.starts_with("::"),
9360        &lexical_scope,
9361    )
9362}
9363
9364fn local_receiver_alias_type_node<'tree>(
9365    node: Node<'tree>,
9366    name: &str,
9367    ctx: &ScanCtx<'_>,
9368) -> Option<Node<'tree>> {
9369    let callable = nearest_callable_scope(node)?;
9370    let mut root_callable = callable;
9371    // See `target_guided_nested_type_terminal_hit`: the result carries the
9372    // caller's tree lifetime.
9373    let mut ancestor = callable.parent();
9374    while let Some(current) = ancestor {
9375        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9376            root_callable = current;
9377        }
9378        ancestor = current.parent();
9379    }
9380
9381    let mut stack = vec![root_callable];
9382    let mut best = None;
9383    while let Some(current) = stack.pop() {
9384        if current.start_byte() >= node.start_byte() {
9385            continue;
9386        }
9387        if local_type_alias_name_node(current)
9388            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9389            && current
9390                .parent()
9391                .is_none_or(|parent| parent.kind() != "template_declaration")
9392            && has_function_scope_ancestor(current)
9393            && local_alias_scope_contains_node(current, node)
9394        {
9395            let replace = best
9396                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9397            if replace {
9398                best = current.child_by_field_name("type");
9399            }
9400        }
9401        let mut cursor = current.walk();
9402        stack.extend(current.named_children(&mut cursor));
9403    }
9404    best
9405}
9406
9407fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9408    let mut canonical = Vec::with_capacity(units.len());
9409    for unit in units {
9410        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9411            return Vec::new();
9412        };
9413        canonical.push(unit);
9414    }
9415    unanimous_receiver_units(canonical)
9416}
9417
9418fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9419    if let Some(cached) = ctx
9420        .receiver_canonical_type_cache
9421        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
9422            cells.borrow().get(unit).cloned()
9423        })
9424    {
9425        return cached;
9426    }
9427    let canonical = ctx
9428        .visibility
9429        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9430    ctx.receiver_canonical_type_cache
9431        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
9432            cells.borrow_mut().insert(unit.clone(), canonical.clone())
9433        });
9434    canonical
9435}
9436
9437fn receiver_units_from_declared_fields(
9438    fields: Vec<&CodeUnit>,
9439    reference: Node<'_>,
9440    ctx: &ScanCtx<'_>,
9441) -> Vec<CodeUnit> {
9442    let Some(first) = fields.first() else {
9443        return Vec::new();
9444    };
9445    if fields
9446        .iter()
9447        .skip(1)
9448        .any(|field| !same_visible_symbol(first, field))
9449    {
9450        return Vec::new();
9451    }
9452    unanimous_receiver_units(
9453        fields
9454            .into_iter()
9455            .filter_map(|field| {
9456                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9457                    .and_then(|binding| binding.unit)
9458                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9459            })
9460            .collect(),
9461    )
9462}
9463
9464/// Resolve a field receiver's declared type from its structured declaration
9465/// when the persisted type fact was built under a malformed sentinel scope.
9466/// The queried member owner supplies the exact class identity to prove; the
9467/// declaration's type node and recovered lexical path provide the evidence.
9468fn recovered_receiver_field_type(
9469    reference: Node<'_>,
9470    field: &CodeUnit,
9471    ctx: &ScanCtx<'_>,
9472) -> Option<CodeUnit> {
9473    let target = ctx.spec.owner.as_ref()?.clone();
9474    if !target.is_class() || field.source() != ctx.file {
9475        return None;
9476    }
9477    let range = ctx
9478        .analyzer
9479        .ranges(field)
9480        .into_iter()
9481        .find(|range| range.start_byte < range.end_byte)?;
9482    let mut declaration =
9483        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9484    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9485        declaration = ctx.ancestry.parent(declaration)?;
9486    }
9487    let type_node = first_type_child(declaration)?;
9488    let resolution = resolve_type_node_lexically_for_target(
9489        type_node,
9490        &ctx.analyzer,
9491        ctx.visibility,
9492        &ctx.ordinary_type_imports,
9493        ctx.file,
9494        ctx.source,
9495        &target,
9496        Some(&ctx.lexical_scope_cache),
9497        ctx.recovered_sentinel_scope(type_node).as_deref(),
9498    );
9499    if let LexicalTypeResolution::Resolved {
9500        unit, candidates, ..
9501    } = resolution
9502        && (same_visible_symbol(&unit, &target)
9503            || candidates
9504                .iter()
9505                .any(|candidate| same_visible_symbol(candidate, &target)))
9506    {
9507        return Some(target);
9508    }
9509    let type_node = receiver_type_node_base(type_node);
9510    let (components, global) = type_reference_components(type_node, ctx.source)?;
9511    let scope = ctx
9512        .recovered_sentinel_scope(type_node)
9513        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9514    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9515        && ctx.visibility.external_type_candidate_visible_in_context(
9516            &ctx.analyzer,
9517            ctx.file,
9518            &target,
9519            type_node,
9520        ))
9521    .then_some(target)
9522}
9523
9524fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9525    let mut unique = Vec::new();
9526    for unit in units {
9527        if !unique
9528            .iter()
9529            .any(|existing| same_visible_symbol(existing, &unit))
9530        {
9531            unique.push(unit);
9532            if unique.len() > 1 {
9533                return Vec::new();
9534            }
9535        }
9536    }
9537    unique
9538}
9539
9540fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9541    let Some(owner) = ctx.spec.owner.as_ref() else {
9542        return false;
9543    };
9544    match node.kind() {
9545        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9546        // When that structured lookup fails, the base expression's owner is not the field's
9547        // type: treating `value_` as `Value` would misclassify
9548        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9549        // with the `this` node itself, so failing closed for an unresolved field chain does not
9550        // discard genuine self calls.
9551        "field_expression" => false,
9552        "call_expression" => node
9553            .child_by_field_name("function")
9554            .is_some_and(|function| receiver_matches_target(function, ctx)),
9555        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9556            .child_by_field_name("argument")
9557            .or_else(|| node.named_child(0))
9558            .is_some_and(|child| receiver_matches_target(child, ctx)),
9559        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9560            .bindings
9561            .resolve_symbol(node_text(node, ctx.source))
9562            .as_precise()
9563            .is_some_and(|targets| {
9564                targets
9565                    .iter()
9566                    .filter_map(|target| target.unit.as_ref())
9567                    .any(|target| {
9568                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9569                    })
9570            }),
9571        "this" => same_owner_context(node, ctx),
9572        _ => qualified_owner_matches(node, ctx),
9573    }
9574}
9575
9576fn declaring_owner_for_explicit_receiver(
9577    receiver: Node<'_>,
9578    call_arity: Option<usize>,
9579    ctx: &ScanCtx<'_>,
9580) -> EnclosingMemberOwnerResolution {
9581    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9582        return EnclosingMemberOwnerResolution::Missing;
9583    }
9584    declaring_owner_from_receiver_units(
9585        receiver_type_units(receiver, ctx.source, ctx),
9586        receiver.start_byte(),
9587        call_arity,
9588        ctx,
9589    )
9590}
9591
9592/// The owner that declares the queried member for a receiver already typed.
9593///
9594/// `reference_byte` is the reference's position in the scanned file, which
9595/// decides which declarations are visible there. A receiver recovered from a
9596/// macro replacement is typed against the sentinel parse but still reported at
9597/// its bytes in the defining file, so the two arrive separately.
9598fn declaring_owner_from_receiver_units(
9599    receiver_units: Vec<CodeUnit>,
9600    reference_byte: usize,
9601    call_arity: Option<usize>,
9602    ctx: &ScanCtx<'_>,
9603) -> EnclosingMemberOwnerResolution {
9604    let mut declaring_owner = None;
9605    for receiver_owner in receiver_units {
9606        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9607            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9608        }) {
9609            if declaring_owner
9610                .as_ref()
9611                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9612            {
9613                return EnclosingMemberOwnerResolution::Ambiguous;
9614            }
9615            declaring_owner = Some(receiver_owner);
9616            continue;
9617        }
9618        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9619        let owner_resolution = match call_arity {
9620            Some(arity) => resolve_declaring_callable_owner(
9621                &ctx.analyzer,
9622                ctx.visibility,
9623                ctx.file,
9624                ordinary,
9625                &ctx.spec.member_name,
9626                arity,
9627            ),
9628            None => ordinary,
9629        };
9630        match owner_resolution {
9631            EnclosingMemberOwnerResolution::Owner(owner) => {
9632                if declaring_owner
9633                    .as_ref()
9634                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9635                {
9636                    return EnclosingMemberOwnerResolution::Ambiguous;
9637                }
9638                declaring_owner = Some(owner);
9639            }
9640            EnclosingMemberOwnerResolution::Ambiguous => {
9641                return EnclosingMemberOwnerResolution::Ambiguous;
9642            }
9643            EnclosingMemberOwnerResolution::Missing => {}
9644        }
9645    }
9646    declaring_owner
9647        .map(EnclosingMemberOwnerResolution::Owner)
9648        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9649}
9650
9651fn declaring_owner_from_call_function(
9652    function: Node<'_>,
9653    call_arity: Option<usize>,
9654    ctx: &ScanCtx<'_>,
9655) -> Option<EnclosingMemberOwnerResolution> {
9656    match function.kind() {
9657        "field_expression" => function
9658            .child_by_field_name("argument")
9659            .or_else(|| function.child_by_field_name("object"))
9660            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9661            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9662        "call_expression" => function
9663            .child_by_field_name("function")
9664            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9665        _ => None,
9666    }
9667}
9668
9669enum MethodReceiverTargetResolution {
9670    Target,
9671    NonTarget,
9672    Ambiguous,
9673    Missing,
9674}
9675
9676fn method_receiver_target_resolution(
9677    node: Node<'_>,
9678    declaring_owner: EnclosingMemberOwnerResolution,
9679    ctx: &ScanCtx<'_>,
9680) -> MethodReceiverTargetResolution {
9681    match declaring_owner {
9682        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9683            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9684        }
9685        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9686            MethodReceiverTargetResolution::Missing
9687        }
9688        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9689            MethodReceiverTargetResolution::Target
9690        }
9691        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9692            MethodReceiverTargetResolution::NonTarget
9693        }
9694        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9695    }
9696}
9697
9698/// Decide the queried member against a receiver's declaring owner alone.
9699///
9700/// This is the part of receiver typing that needs nothing but the owner and
9701/// the reference's position, so a member call recovered from a macro
9702/// replacement reaches the same verdict as ordinary code even though its
9703/// syntax lives in a separate sentinel parse.
9704fn declaring_owner_target_resolution(
9705    declaring_owner: EnclosingMemberOwnerResolution,
9706    reference_byte: usize,
9707    ctx: &ScanCtx<'_>,
9708) -> MethodReceiverTargetResolution {
9709    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9710        return MethodReceiverTargetResolution::Missing;
9711    };
9712    match declaring_owner {
9713        EnclosingMemberOwnerResolution::Owner(owner)
9714            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9715        {
9716            MethodReceiverTargetResolution::Target
9717        }
9718        EnclosingMemberOwnerResolution::Owner(owner)
9719            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9720        {
9721            MethodReceiverTargetResolution::NonTarget
9722        }
9723        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9724            MethodReceiverTargetResolution::Missing
9725        }
9726        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9727    }
9728}
9729
9730fn explicit_receiver_target_resolution(
9731    receiver: Node<'_>,
9732    call_arity: Option<usize>,
9733    ctx: &ScanCtx<'_>,
9734) -> MethodReceiverTargetResolution {
9735    method_receiver_target_resolution(
9736        receiver,
9737        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9738        ctx,
9739    )
9740}
9741
9742fn call_function_target_resolution(
9743    function: Node<'_>,
9744    ctx: &ScanCtx<'_>,
9745) -> MethodReceiverTargetResolution {
9746    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9747        (call.kind() == "call_expression")
9748            .then(|| {
9749                ctx.visibility
9750                    .call_arity_evidence(ctx.file, call, ctx.source)
9751                    .exact()
9752            })
9753            .flatten()
9754    });
9755    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9756    else {
9757        // A bare function identifier has an implicit receiver. Do not reinterpret
9758        // that identifier as a same-named type or value before enclosing-owner
9759        // lookup gets a chance to establish the member call.
9760        return MethodReceiverTargetResolution::Missing;
9761    };
9762    method_receiver_target_resolution(function, declaring_owner, ctx)
9763}
9764
9765fn receiver_owner_matches_target(
9766    receiver_owner: &CodeUnit,
9767    target_owner: &CodeUnit,
9768    reference_byte: usize,
9769    ctx: &ScanCtx<'_>,
9770) -> bool {
9771    same_symbol(receiver_owner, target_owner)
9772        || same_logical_symbol(receiver_owner, target_owner)
9773            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9774                || (ctx.spec.owner_is_forward_declaration
9775                    && ctx
9776                        .visibility
9777                        .is_physically_visible(ctx.file, receiver_owner))
9778                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9779                || target_group_contains_owner_peer(receiver_owner, ctx)
9780                // An identical definition of the class in a header this file
9781                // includes is the target's owner too, the relation forward
9782                // definition lookup crosses from the body's side (#6902).
9783                || ctx.visibility.same_logical_class_definition(
9784                    &ctx.analyzer,
9785                    receiver_owner,
9786                    target_owner,
9787                ))
9788}
9789
9790fn receiver_owner_is_known_non_target(
9791    receiver_owner: &CodeUnit,
9792    target_owner: &CodeUnit,
9793    reference_byte: usize,
9794    ctx: &ScanCtx<'_>,
9795) -> bool {
9796    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9797        return false;
9798    }
9799    if !same_logical_symbol(receiver_owner, target_owner) {
9800        return true;
9801    }
9802    !ctx.target_group.iter().any(|target| {
9803        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9804    })
9805}
9806
9807fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9808    ctx.visibility
9809        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9810        && ctx.target_group.iter().any(|target| {
9811            type_owner_of(&ctx.analyzer, target)
9812                .as_ref()
9813                .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9814        })
9815}
9816
9817fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9818    same_symbol(candidate_owner, owner)
9819        || (same_logical_symbol(candidate_owner, owner)
9820            && candidate_owner.source() == owner.source())
9821}
9822
9823fn visible_target_peer_matches_owner(
9824    owner: &CodeUnit,
9825    reference_byte: usize,
9826    ctx: &ScanCtx<'_>,
9827) -> bool {
9828    ctx.visibility
9829        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9830        && ctx
9831            .visibility
9832            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9833            .any(|candidate| {
9834                cpp_callable_definitions_share_identity_evidence(
9835                    &ctx.analyzer,
9836                    candidate,
9837                    &ctx.spec.target,
9838                ) && ctx.visibility.declaration_visible_at(
9839                    &ctx.analyzer,
9840                    ctx.file,
9841                    candidate,
9842                    reference_byte,
9843                ) && type_owner_of(&ctx.analyzer, candidate)
9844                    .as_ref()
9845                    .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9846            })
9847}
9848
9849fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9850    match function.kind() {
9851        "field_expression" => function
9852            .child_by_field_name("argument")
9853            .or_else(|| function.child_by_field_name("object"))
9854            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9855        _ => receiver_is_self_like(function, reference_is_c),
9856    }
9857}
9858
9859fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9860    let Some(owner) = ctx.spec.owner.as_ref() else {
9861        return false;
9862    };
9863    match node.kind() {
9864        "field_expression" => node
9865            .child_by_field_name("argument")
9866            .or_else(|| node.child_by_field_name("object"))
9867            .is_some_and(|receiver| {
9868                // `this` names the enclosing class, not necessarily the class that
9869                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9870                // lookup decide whether this is a same-owner call, an inherited base
9871                // member, a nearer override, or an ambiguous base. Rejecting a derived
9872                // `this` merely because its immediate type differs from the target owner
9873                // drops genuine inherited calls (#2541).
9874                if receiver_is_self_like(
9875                    receiver,
9876                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9877                ) {
9878                    return false;
9879                }
9880                if receiver_is_known_function_declarator(receiver, ctx) {
9881                    return true;
9882                }
9883                let units = receiver_type_units(receiver, ctx.source, ctx);
9884                !units.is_empty()
9885                    && units.iter().all(|target| {
9886                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9887                    })
9888            }),
9889        "call_expression" => node
9890            .child_by_field_name("function")
9891            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9892        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9893            .child_by_field_name("argument")
9894            .or_else(|| node.named_child(0))
9895            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9896        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9897            .bindings
9898            .resolve_symbol(node_text(node, ctx.source))
9899            .as_precise()
9900            .is_some_and(|targets| {
9901                let units = targets
9902                    .iter()
9903                    .filter_map(|target| target.unit.as_ref())
9904                    .collect::<Vec<_>>();
9905                !units.is_empty()
9906                    && units.iter().all(|target| {
9907                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9908                    })
9909            }),
9910        "this" => known_non_target_owner_context(node, ctx),
9911        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9912            qualified_owner_is_known_non_target(node, ctx)
9913        }
9914        _ => false,
9915    }
9916}
9917
9918fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9919    if receiver.kind() != "identifier" {
9920        return false;
9921    }
9922    let receiver_name = node_text(receiver, ctx.source);
9923    let mut current = receiver;
9924    while let Some(parent) = ctx.ancestry.parent(current) {
9925        if parent.kind() == "compound_statement" {
9926            let mut cursor = parent.walk();
9927            for statement in parent.named_children(&mut cursor) {
9928                if statement.start_byte() >= receiver.start_byte() {
9929                    break;
9930                }
9931                if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9932                {
9933                    continue;
9934                }
9935                let type_text = statement
9936                    .child_by_field_name("type")
9937                    .or_else(|| first_type_child(statement))
9938                    .map(|type_node| node_text(type_node, ctx.source));
9939                let mut declaration_cursor = statement.walk();
9940                for child in statement.named_children(&mut declaration_cursor) {
9941                    let declarator = if child.kind() == "init_declarator" {
9942                        child.child_by_field_name("declarator")
9943                    } else if is_declarator_node(child) {
9944                        Some(child)
9945                    } else {
9946                        None
9947                    };
9948                    let Some(declarator) = declarator else {
9949                        continue;
9950                    };
9951                    if declarator.kind() == "function_declarator"
9952                        && extract_variable_name(declarator, ctx.source)
9953                            .is_some_and(|name| name == receiver_name)
9954                        && function_declarator_parameters_have_known_types(declarator, ctx)
9955                        && !constructor_style_local_declaration(
9956                            ctx.visibility,
9957                            ctx.file,
9958                            ctx.source,
9959                            declarator,
9960                            type_text,
9961                            &ctx.bindings,
9962                        )
9963                    {
9964                        return true;
9965                    }
9966                }
9967            }
9968            return false;
9969        }
9970        current = parent;
9971    }
9972    false
9973}
9974
9975fn function_declarator_parameters_have_known_types(
9976    declarator: Node<'_>,
9977    ctx: &ScanCtx<'_>,
9978) -> bool {
9979    let Some(parameters) = declarator.child_by_field_name("parameters") else {
9980        return false;
9981    };
9982    let mut cursor = parameters.walk();
9983    parameters.named_children(&mut cursor).all(|parameter| {
9984        if !matches!(
9985            parameter.kind(),
9986            "parameter_declaration" | "optional_parameter_declaration"
9987        ) {
9988            return false;
9989        }
9990        let Some(type_node) = parameter
9991            .child_by_field_name("type")
9992            .or_else(|| first_type_child(parameter))
9993        else {
9994            return false;
9995        };
9996        type_node.kind() == "primitive_type"
9997            || ctx
9998                .visibility
9999                .resolve_type_node_result(ctx.file, type_node, ctx.source)
10000                .ok()
10001                .flatten()
10002                .is_some()
10003    })
10004}
10005
10006#[derive(Clone, Copy, PartialEq, Eq)]
10007enum QualifiedOwnerResolution {
10008    Target,
10009    NonTarget,
10010    Unresolved,
10011}
10012
10013#[derive(Clone)]
10014pub enum LexicalScopeResolution {
10015    Resolved(Vec<String>),
10016    Ambiguous,
10017    Missing,
10018}
10019
10020/// One scan's memo of lexical scope reconstructions for one file, keyed by
10021/// the reconstruction's anchor range and flags, with the file's recovered
10022/// namespace regions that the key and the reconstruction both read.
10023type LexicalScopeResolutions = HashMap<(usize, usize, bool, bool), LexicalScopeResolution>;
10024
10025pub struct LexicalScopeCache {
10026    resolutions: CaptureScopedMemo<RefCell<LexicalScopeResolutions>>,
10027    orphaned: Arc<OrphanedNamespaceScopeIndex>,
10028}
10029
10030impl LexicalScopeCache {
10031    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
10032        Self {
10033            resolutions: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
10034            orphaned: orphaned_namespace_scopes(visibility, file),
10035        }
10036    }
10037}
10038
10039/// The namespaces parse recovery dropped from `file`, memoized on the
10040/// analyzer (issue #1537).
10041fn orphaned_namespace_scopes(
10042    visibility: &VisibilityIndex<'_>,
10043    file: &ProjectFile,
10044) -> Arc<OrphanedNamespaceScopeIndex> {
10045    visibility
10046        .cpp()
10047        .orphaned_namespace_scopes(visibility.token(), file)
10048}
10049
10050fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10051    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
10052}
10053
10054fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10055    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
10056}
10057
10058fn is_structurally_qualified(node: Node<'_>) -> bool {
10059    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10060        && qualified_name_has_concrete_scope_separators(node)
10061}
10062
10063fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
10064    let Some(target_owner) = ctx.spec.owner.as_ref() else {
10065        return QualifiedOwnerResolution::Unresolved;
10066    };
10067    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
10068        return QualifiedOwnerResolution::Unresolved;
10069    };
10070    // A malformed wrapper can make the parser-derived enclosing owner look
10071    // like the lexical namespace (for example tinyxml2's macro-prefixed
10072    // XMLHandle declarations). Recover the target namespace only while this
10073    // function owns scope reconstruction. An explicit recovered scope is
10074    // authoritative and enters the scoped resolver directly.
10075    if !global
10076        && !matches!(
10077            enclosing_lexical_scope_components(
10078                node,
10079                &ctx.analyzer,
10080                ctx.visibility,
10081                ctx.file,
10082                ctx.source,
10083            ),
10084            LexicalScopeResolution::Resolved(_)
10085        )
10086    {
10087        return QualifiedOwnerResolution::Unresolved;
10088    }
10089    match resolve_type_components_lexically_at_for_target_with_scope_cache(
10090        node,
10091        &components,
10092        global,
10093        &ctx.analyzer,
10094        ctx.visibility,
10095        &ctx.ordinary_type_imports,
10096        ctx.file,
10097        ctx.source,
10098        target_owner,
10099        false,
10100        Some(&ctx.lexical_scope_cache),
10101    ) {
10102        LexicalTypeResolution::Resolved { unit: owner, .. } => {
10103            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
10104                return QualifiedOwnerResolution::Target;
10105            }
10106            match cached_declaring_member_owner(&owner, ctx) {
10107                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10108                    if receiver_owner_matches_target(
10109                        &declaring_owner,
10110                        target_owner,
10111                        node.start_byte(),
10112                        ctx,
10113                    ) =>
10114                {
10115                    QualifiedOwnerResolution::Target
10116                }
10117                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10118                    if receiver_owner_is_known_non_target(
10119                        &declaring_owner,
10120                        target_owner,
10121                        node.start_byte(),
10122                        ctx,
10123                    ) =>
10124                {
10125                    QualifiedOwnerResolution::NonTarget
10126                }
10127                EnclosingMemberOwnerResolution::Owner(_)
10128                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
10129                EnclosingMemberOwnerResolution::Missing
10130                    if same_visible_symbol(&owner, target_owner) =>
10131                {
10132                    QualifiedOwnerResolution::Unresolved
10133                }
10134                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
10135            }
10136        }
10137        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
10138            QualifiedOwnerResolution::Unresolved
10139        }
10140    }
10141}
10142
10143fn qualified_callable_owner_components(
10144    node: Node<'_>,
10145    source: &str,
10146) -> Option<(Vec<String>, bool)> {
10147    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10148        || !qualified_name_has_concrete_scope_separators(node)
10149    {
10150        return None;
10151    }
10152    let owner = qualified_owner_components(node, source)?;
10153    Some((owner.names, owner.global))
10154}
10155
10156fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
10157    if !matches!(
10158        node.kind(),
10159        "identifier"
10160            | "type_identifier"
10161            | "namespace_identifier"
10162            | "qualified_identifier"
10163            | "scoped_identifier"
10164            | "scoped_type_identifier"
10165            | "template_type"
10166            | "template_function"
10167    ) {
10168        return None;
10169    }
10170    let components = cpp_type_name_components(node, source)?;
10171    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
10172}
10173
10174/// The block-scope answer for a type node, when a local class declaration
10175/// decides it: `Some(None)` when a block declares the name but it denotes no
10176/// minted class, so callers must not fall back to an outer type.
10177fn function_local_type_for_node(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<Option<CodeUnit>> {
10178    let (components, global) = type_reference_components(node, ctx.source)?;
10179    if global {
10180        return None;
10181    }
10182    match function_local_type_resolution(&ctx.analyzer, ctx.file, node, &components, ctx.source)? {
10183        LexicalTypeResolution::Resolved { unit, .. } => Some(Some(unit)),
10184        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => Some(None),
10185    }
10186}
10187
10188pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
10189    let mut namespaces = Vec::new();
10190    for parent in enclosing_cpp_namespace_nodes(node) {
10191        if let Some(name) = parent.child_by_field_name("name") {
10192            let mut components = Vec::new();
10193            if append_cpp_name_components(name, source, &mut components).is_some() {
10194                namespaces.push(components);
10195            }
10196        }
10197    }
10198    namespaces.into_iter().flatten().collect()
10199}
10200
10201pub fn enclosing_lexical_scope_components(
10202    node: Node<'_>,
10203    analyzer: &CppGraphSource<'_>,
10204    visibility: &VisibilityIndex<'_>,
10205    file: &ProjectFile,
10206    source: &str,
10207) -> LexicalScopeResolution {
10208    enclosing_lexical_scope_components_with_unresolved_owner(
10209        node,
10210        analyzer,
10211        visibility,
10212        file,
10213        source,
10214        false,
10215        false,
10216        &orphaned_namespace_scopes(visibility, file),
10217    )
10218}
10219
10220#[allow(clippy::too_many_arguments)]
10221fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
10222    node: Node<'_>,
10223    analyzer: &CppGraphSource<'_>,
10224    visibility: &VisibilityIndex<'_>,
10225    file: &ProjectFile,
10226    source: &str,
10227    allow_structured_unresolved_owner: bool,
10228    ignore_function_owner: bool,
10229    cache: Option<&LexicalScopeCache>,
10230) -> LexicalScopeResolution {
10231    let Some(cache) = cache else {
10232        return enclosing_lexical_scope_components_with_unresolved_owner(
10233            node,
10234            analyzer,
10235            visibility,
10236            file,
10237            source,
10238            allow_structured_unresolved_owner,
10239            ignore_function_owner,
10240            &orphaned_namespace_scopes(visibility, file),
10241        );
10242    };
10243    let orphaned = &cache.orphaned;
10244    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
10245    // A namespace body or translation unit anchor spans every recovered
10246    // region among its children, whose scopes differ; the region bounds keep
10247    // the key exact there and leave an anchor inside one region unchanged.
10248    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
10249        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
10250        None => (anchor_start, anchor_end),
10251    };
10252    let key = (
10253        anchor_start,
10254        anchor_end,
10255        allow_structured_unresolved_owner,
10256        ignore_function_owner,
10257    );
10258    if let Some(cached) = cache
10259        .resolutions
10260        .read(&visibility.reads.memo_attribution(), |cells| {
10261            cells.borrow().get(&key).cloned()
10262        })
10263    {
10264        return cached;
10265    }
10266    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
10267        node,
10268        analyzer,
10269        visibility,
10270        file,
10271        source,
10272        allow_structured_unresolved_owner,
10273        ignore_function_owner,
10274        orphaned,
10275    );
10276    cache
10277        .resolutions
10278        .read(&visibility.reads.memo_attribution(), |cells| {
10279            cells.borrow_mut().insert(key, resolved.clone())
10280        });
10281    resolved
10282}
10283
10284fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
10285    let mut current = node;
10286    loop {
10287        if matches!(
10288            current.kind(),
10289            "function_definition"
10290                | "class_specifier"
10291                | "struct_specifier"
10292                | "union_specifier"
10293                | "namespace_definition"
10294                | "translation_unit"
10295        ) {
10296            return (current.start_byte(), current.end_byte());
10297        }
10298        let Some(parent) = current.parent() else {
10299            return (current.start_byte(), current.end_byte());
10300        };
10301        current = parent;
10302    }
10303}
10304
10305#[allow(clippy::too_many_arguments)]
10306fn enclosing_lexical_scope_components_with_unresolved_owner(
10307    node: Node<'_>,
10308    analyzer: &CppGraphSource<'_>,
10309    visibility: &VisibilityIndex<'_>,
10310    file: &ProjectFile,
10311    source: &str,
10312    allow_structured_unresolved_owner: bool,
10313    ignore_function_owner: bool,
10314    orphaned: &OrphanedNamespaceScopeIndex,
10315) -> LexicalScopeResolution {
10316    #[cfg(any(test, feature = "test-support"))]
10317    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
10318    // One ancestor climb collects the namespace chain, the class chain, the
10319    // nearest function definition and both displaced-class-shape facts.
10320    // `Node::parent` re-descends from the root on every call (tree-sitter
10321    // 0.24+), so the four separate climbs this replaces each cost another
10322    // near-full-AST scan per reconstruction on a large flat file (#1927).
10323    let mut namespaces = Vec::new();
10324    let mut classes = Vec::new();
10325    let mut function_definition = None;
10326    let mut displaced_class_scope = false;
10327    let mut current = node.parent();
10328    while let Some(parent) = current {
10329        match parent.kind() {
10330            "namespace_definition" => {
10331                if let Some(name) = parent.child_by_field_name("name") {
10332                    let mut components = Vec::new();
10333                    if append_cpp_name_components(name, source, &mut components).is_some() {
10334                        namespaces.push((
10335                            parent.start_byte(),
10336                            components,
10337                            namespace_definition_has_real_braces(parent),
10338                        ));
10339                    }
10340                }
10341            }
10342            "class_specifier" | "struct_specifier" | "union_specifier" => {
10343                if let Some(name) = parent.child_by_field_name("name") {
10344                    let mut components = Vec::new();
10345                    if append_cpp_name_components(name, source, &mut components).is_some() {
10346                        classes.push(components);
10347                    }
10348                }
10349            }
10350            "function_definition" => {
10351                if function_definition.is_none() {
10352                    function_definition = Some(parent);
10353                }
10354                displaced_class_scope = displaced_class_scope
10355                    || parent.child_by_field_name("type").is_some_and(|type_node| {
10356                        matches!(
10357                            type_node.kind(),
10358                            "class_specifier" | "struct_specifier" | "union_specifier"
10359                        )
10360                    })
10361                    || is_malformed_wrapper_function_definition(parent);
10362            }
10363            _ => {}
10364        }
10365        current = parent.parent();
10366    }
10367    namespaces.reverse();
10368    // The parsed chain can stop short of the namespaces tree-sitter's error
10369    // recovery closed early; the orphaned index restores them (#1537).
10370    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
10371    let mut scope = namespace.clone();
10372    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
10373    // qualified function declarator. It is recovery scaffolding, not a real
10374    // callable owner, and must not overwrite the indexed class scope retained
10375    // by the wrapper body (#2249).
10376    let has_qualified_function_owner = function_definition
10377        .filter(|function| !is_malformed_wrapper_function_definition(*function))
10378        .and_then(function_definition_owner_lookup_node)
10379        .is_some_and(|owner| {
10380            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10381        });
10382    let indexed_scope = displaced_class_scope
10383        .then(|| {
10384            indexed_structural_class_scope(visibility, file, node, source)
10385                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10386        })
10387        .flatten()
10388        .or_else(|| {
10389            // A qualified out-of-line definition can lose its class owner from
10390            // the parser tree when a namespace sentinel or export macro wraps
10391            // the declaration.  Recover the indexed owner scope up front so
10392            // all unqualified type references in the body see the same class
10393            // boundary as C++ lookup, including aliases in parameters and
10394            // local declarations (not only template-argument leaves).
10395            (has_qualified_function_owner && function_definition.is_some())
10396                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10397                .flatten()
10398                .filter(|indexed| {
10399                    qualified_owner_scope_is_recoverable(
10400                        indexed,
10401                        &namespace,
10402                        &classes,
10403                        function_definition
10404                            .and_then(function_definition_owner_lookup_node)
10405                            .and_then(|owner| qualified_callable_owner_components(owner, source))
10406                            .map(|(components, _)| components),
10407                    )
10408                })
10409        })
10410        .or_else(|| {
10411            // A nested class declaration can likewise lose one of its outer
10412            // class ancestors from the CST.  Prefer the exact indexed
10413            // structural class scope. Its same-file declaration range and
10414            // exact template-id match already prove the owner, including a
10415            // specialization whose parser component retains only the primary
10416            // name. Keep the suffix guard for graph-only recovery, which lacks
10417            // that direct syntax-range proof.
10418            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10419                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10420                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10421                })
10422            })
10423        })
10424        .or_else(|| {
10425            // Retain the existing indexed lexical-scope recovery for
10426            // unqualified function bodies.  It is intentionally last so a
10427            // canonical class owner wins whenever one is available.
10428            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10429                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10430                .flatten()
10431                .filter(|indexed| indexed.len() > namespace.len())
10432        });
10433    if let Some(indexed_scope) = indexed_scope.as_ref() {
10434        // A macro-displaced namespace can leave the parser with the real class
10435        // body but no namespace ancestor. Prefer the structural class match;
10436        // partial specializations whose structured name cannot round-trip use
10437        // the exact indexed enclosing-owner chain instead.
10438        scope = indexed_scope.clone();
10439        classes.clear();
10440    }
10441
10442    if !ignore_function_owner
10443        && has_qualified_function_owner
10444        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10445    {
10446        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10447            return LexicalScopeResolution::Missing;
10448        };
10449        // Resolve the out-of-line owner from the parser namespace before the
10450        // provisional indexed parent can influence the answer. Per-file
10451        // extraction can assign the first same-depth using namespace to a
10452        // bare owner. The structured using resolver instead selects the
10453        // namespace whose visible class has the owner name (#1838).
10454        let imports = visibility.ordinary_type_import_cell(file);
10455        let owner_resolution = resolve_type_components_lexically_at_scoped(
10456            function,
10457            &owner,
10458            global,
10459            analyzer,
10460            visibility,
10461            &imports,
10462            file,
10463            source,
10464            None,
10465            false,
10466            false,
10467            false,
10468            namespace.clone(),
10469        );
10470        match owner_resolution {
10471            LexicalTypeResolution::Resolved {
10472                unit, components, ..
10473            } if is_indexed_class_owner(analyzer, &unit) => {
10474                scope = components;
10475                classes.clear();
10476            }
10477            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10478            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10479                match visibility
10480                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10481                {
10482                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10483                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10484                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10485                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10486                            qualified_owner_scope_is_recoverable(
10487                                indexed,
10488                                &namespace,
10489                                &classes,
10490                                Some(owner.clone()),
10491                            )
10492                        }) {
10493                            scope = indexed.clone();
10494                        } else {
10495                            scope = if global || owner.starts_with(&namespace) {
10496                                owner
10497                            } else {
10498                                let mut relative = namespace;
10499                                relative.extend(owner);
10500                                relative
10501                            };
10502                        }
10503                    }
10504                    LexicalTypeResolution::Missing => {
10505                        // Structural lexical resolution cannot see an owner class that
10506                        // is reachable only through an in-scope `using namespace`
10507                        // directive, so it would otherwise hard-fail here. The indexed
10508                        // definition already carries the true fully-qualified owner
10509                        // (its package reflects the directive), so recover the real
10510                        // enclosing scope from the analyzer graph -- exactly the scope
10511                        // chain real C++ unqualified lookup traverses. Only the strict
10512                        // callers reach this arm; the best-effort callers above keep
10513                        // their existing structural guess (and its query profile).
10514                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10515                            .or_else(|| {
10516                                indexed_namespace_qualified_scope(
10517                                    analyzer, visibility, file, node, &owner,
10518                                )
10519                            }) {
10520                            Some(indexed) => scope = indexed,
10521                            None => return LexicalScopeResolution::Missing,
10522                        }
10523                    }
10524                }
10525            }
10526        }
10527    }
10528
10529    classes.reverse();
10530    scope.extend(classes.into_iter().flatten());
10531    LexicalScopeResolution::Resolved(scope)
10532}
10533
10534fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10535    let mut current = node.parent();
10536    while let Some(parent) = current {
10537        if parent.kind() == "function_definition"
10538            && parent.child_by_field_name("type").is_some_and(|type_node| {
10539                matches!(
10540                    type_node.kind(),
10541                    "class_specifier" | "struct_specifier" | "union_specifier"
10542                )
10543            })
10544        {
10545            return true;
10546        }
10547        current = parent.parent();
10548    }
10549    false
10550}
10551
10552fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10553    let mut current = node.parent();
10554    while let Some(parent) = current {
10555        if parent.kind() == "function_definition"
10556            && is_malformed_wrapper_function_definition(parent)
10557        {
10558            return true;
10559        }
10560        current = parent.parent();
10561    }
10562    false
10563}
10564
10565/// Whether a function definition is a recovery wrapper with a non-function
10566/// declarator. The older grammar's evidence is parser damage; the
10567/// macro-declaration grammar keeps the same envelope clean, where a
10568/// `macro_modifier`/`macro_modifier_declarator`/`macro_type_specifier` head is
10569/// admitted only where the parse would otherwise contain an error.
10570pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10571    let mut cursor = node.walk();
10572    let recovery_evidence = node.has_error()
10573        || node.named_children(&mut cursor).any(|child| {
10574            matches!(
10575                child.kind(),
10576                "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10577            )
10578        });
10579    recovery_evidence
10580        && node
10581            .child_by_field_name("declarator")
10582            .is_some_and(|declarator| {
10583                declarator.kind() != "function_declarator"
10584                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10585            })
10586}
10587
10588/// Tree-sitter can make an attribute/nullability macro look like the namespace
10589/// component of a qualified function owner when it appears between the return
10590/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10591/// The recovered owner is not a C++ lexical owner, so callers resolving the
10592/// ordinary return/parameter type must retain the surrounding namespace scope.
10593fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10594    node.child_by_field_name("scope")
10595        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10596        .is_some()
10597}
10598
10599fn indexed_structural_class_scope(
10600    visibility: &VisibilityIndex<'_>,
10601    file: &ProjectFile,
10602    node: Node<'_>,
10603    source: &str,
10604) -> Option<Vec<String>> {
10605    let mut current = node.parent();
10606    while let Some(parent) = current {
10607        if matches!(
10608            parent.kind(),
10609            "class_specifier" | "struct_specifier" | "union_specifier"
10610        ) {
10611            return visibility.indexed_structural_class_scope(file, parent, source);
10612        }
10613        current = parent.parent();
10614    }
10615    None
10616}
10617
10618/// Check that an indexed owner scope is a structured completion of the parser
10619/// scope rather than an unrelated same-spelled declaration.
10620///
10621/// Error recovery around C++ namespace sentinels can preserve only a subset of
10622/// the namespace/class chain.  The indexed definition still carries the full
10623/// owner path, so require every surviving parser component to occur in order
10624/// and require any explicit qualified function owner to be the terminal
10625/// suffix.  An empty parser scope is accepted only with that qualified-owner
10626/// suffix evidence; a lone top-level short name is not evidence that a
10627/// namespace was lost.
10628fn qualified_owner_scope_is_recoverable(
10629    indexed: &[String],
10630    namespace: &[String],
10631    classes: &[Vec<String>],
10632    qualified_owner: Option<Vec<String>>,
10633) -> bool {
10634    if let Some(owner) = qualified_owner {
10635        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10636            return false;
10637        }
10638        // A malformed namespace sentinel can erase every parser namespace
10639        // ancestor.  The indexed enclosing callable still provides an
10640        // authoritative class owner, so the qualified owner suffix itself is
10641        // enough evidence in that case.  When namespace components survived,
10642        // retain the stricter subsequence check below.
10643        if namespace.is_empty() {
10644            return true;
10645        }
10646        if indexed.len() <= namespace.len() {
10647            return false;
10648        }
10649        return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10650    }
10651    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10652    if !class_components.is_empty() {
10653        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10654    }
10655    if namespace.is_empty() || indexed.len() <= namespace.len() {
10656        return false;
10657    }
10658    super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10659}
10660
10661/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10662/// counts as the true lexical owner recovered from the indexed graph.
10663fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10664    unit.is_class()
10665        && !analyzer
10666            .type_alias_provider()
10667            .is_some_and(|provider| provider.is_type_alias(unit))
10668}
10669
10670/// Recover the enclosing member's true lexical scope from the *indexed*
10671/// definition when structural resolution cannot see the owner class.
10672///
10673/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10674/// {...}`) whose owner class is reachable only through an in-scope `using
10675/// namespace X;` directive cannot be resolved by `resolve_type_components_
10676/// lexically`, which walks structural lexical tiers and never consults
10677/// using-directives. The definition itself, however, is indexed with its true
10678/// fully-qualified identity (its package already reflects the directive), so
10679/// the analyzer graph knows the real owner. Walk from the reference's indexed
10680/// enclosing code unit up to the innermost enclosing class and return that
10681/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10682/// -- exactly the scope chain C++ unqualified lookup traverses.
10683fn indexed_enclosing_owner_scope(
10684    analyzer: &CppGraphSource<'_>,
10685    visibility: &VisibilityIndex<'_>,
10686    file: &ProjectFile,
10687    node: Node<'_>,
10688) -> Option<Vec<String>> {
10689    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10690}
10691
10692/// Recover the lexical scope of an out-of-line definition whose syntactic
10693/// qualifier names a *namespace* rather than a class (`void out::target(int)
10694/// {...}` written at file scope, issue #3096).
10695///
10696/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10697/// nothing here and the strict caller would fail closed for every unqualified
10698/// reference in the body. C++ unqualified lookup inside such a definition
10699/// proceeds from the named namespace outward, exactly as inside a `namespace
10700/// out { ... }` block, and the indexed definition already carries that scope
10701/// (`["out"]` for `out::target`).
10702///
10703/// Take it only when a namespace of that name is visible from the file and the
10704/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10705/// the graph does not hold (`void MissingContainer::call()`) has no such
10706/// namespace, so it still fails closed -- and it is rejected by the in-memory
10707/// visibility index, before any indexed enclosing-unit query.
10708fn indexed_namespace_qualified_scope(
10709    analyzer: &CppGraphSource<'_>,
10710    visibility: &VisibilityIndex<'_>,
10711    file: &ProjectFile,
10712    node: Node<'_>,
10713    owner: &[String],
10714) -> Option<Vec<String>> {
10715    let name = owner.last().expect("a qualified owner has one component");
10716    if !visibility
10717        .visible_identifier_candidates(file, name)
10718        .any(|unit| unit.is_module())
10719    {
10720        return None;
10721    }
10722    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10723    indexed.ends_with(owner).then_some(indexed)
10724}
10725
10726fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10727    let start = enclosing_context(node, ctx).enclosing?;
10728    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10729        ctx.analyzer.parent_of(unit)
10730    })
10731    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10732}
10733
10734/// How a qualified type's qualifier selects the owner of the member it names.
10735pub enum QualifierOwnerSelection {
10736    /// No qualifier applies template arguments or names an alias, or the
10737    /// arguments select the primary template, which the ordinary component
10738    /// lookup already names.
10739    Unchanged,
10740    /// The qualifier selects an owner the plain spelling does not reach: a
10741    /// specialization its arguments name, or the class an alias qualifier
10742    /// denotes. Look the rest of the path up through the owner's own scope
10743    /// components.
10744    Selected(Vec<String>),
10745    /// The arguments cannot be bound, more than one specialization applies,
10746    /// or the alias arms the reference admits disagree. The reference stays
10747    /// unproven rather than falling back to the primary template's member.
10748    Ambiguous,
10749}
10750
10751/// Select the owner a qualifier such as `Owner<int>` in `Owner<int>::Alias`
10752/// names before its member is looked up (#6902 item 9).
10753///
10754/// Component lookup reads a qualified name as plain spellings, so it drops
10755/// the qualifier's template arguments and always reaches the primary
10756/// template's member. The qualifier prefix is resolved lexically to its
10757/// primary template, and the shared argument binding and specialization
10758/// selection ([`VisibilityIndex::resolve_template_arguments`]) pick the class
10759/// those arguments name, the same choice an unqualified `Owner<int>` makes.
10760/// Only the last qualifier that applies arguments is selected; a deeper one,
10761/// as in `A<int>::B<char>::C`, keeps the ordinary lookup. A qualifier without
10762/// arguments may still be an alias of a specialization
10763/// ([`select_alias_qualifier_owner`]).
10764#[allow(clippy::too_many_arguments)]
10765pub fn select_qualifier_owner(
10766    node: Node<'_>,
10767    global: bool,
10768    analyzer: &CppGraphSource<'_>,
10769    visibility: &VisibilityIndex<'_>,
10770    ordinary_type_imports: &OrdinaryTypeImportCell,
10771    file: &ProjectFile,
10772    source: &str,
10773) -> QualifierOwnerSelection {
10774    if !matches!(
10775        node.kind(),
10776        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10777    ) {
10778        return QualifierOwnerSelection::Unchanged;
10779    }
10780    let Some(components) = cpp_name_component_nodes(node) else {
10781        return QualifierOwnerSelection::Unchanged;
10782    };
10783    fn templated(component: Node<'_>) -> Option<Node<'_>> {
10784        component.parent().filter(|parent| {
10785            parent.kind() == "template_type"
10786                && parent.child_by_field_name("name") == Some(component)
10787        })
10788    }
10789    let qualifiers = &components[..components.len().saturating_sub(1)];
10790    let Some((index, template)) = qualifiers
10791        .iter()
10792        .enumerate()
10793        .rev()
10794        .find_map(|(index, component)| templated(*component).map(|template| (index, template)))
10795    else {
10796        return select_alias_qualifier_owner(
10797            node,
10798            &components,
10799            global,
10800            analyzer,
10801            visibility,
10802            file,
10803            source,
10804        );
10805    };
10806    if qualifiers[..index]
10807        .iter()
10808        .any(|component| templated(*component).is_some())
10809    {
10810        return QualifierOwnerSelection::Unchanged;
10811    }
10812    let Some(arguments) = cpp_template_reference_arguments(template, source) else {
10813        return QualifierOwnerSelection::Unchanged;
10814    };
10815    let names = components
10816        .iter()
10817        .map(|component| node_text(*component, source).to_owned())
10818        .collect::<Vec<_>>();
10819    let primary = match resolve_type_components_lexically_at(
10820        node,
10821        &names[..=index],
10822        global,
10823        analyzer,
10824        visibility,
10825        ordinary_type_imports,
10826        file,
10827        source,
10828    ) {
10829        LexicalTypeResolution::Resolved { unit, .. } => unit,
10830        LexicalTypeResolution::Ambiguous => return QualifierOwnerSelection::Ambiguous,
10831        LexicalTypeResolution::Missing => return QualifierOwnerSelection::Unchanged,
10832    };
10833    match visibility.resolve_template_arguments(file, primary.clone(), &arguments) {
10834        Ok(owner) if same_visible_symbol(&owner, &primary) => QualifierOwnerSelection::Unchanged,
10835        Ok(owner) => {
10836            let mut redirected = canonical_cpp_scope_components(&owner);
10837            redirected.extend_from_slice(&names[index + 1..]);
10838            QualifierOwnerSelection::Selected(redirected)
10839        }
10840        Err(_) => QualifierOwnerSelection::Ambiguous,
10841    }
10842}
10843
10844/// The owner an alias qualifier such as `Selected` in `Selected::Alias`
10845/// denotes.
10846///
10847/// `using Selected = Owner<int>;` keeps the arguments in the alias's own
10848/// declaration, so the qualifier resolves through the alias to the selected
10849/// specialization, never to the primary template that the spelling `Owner`
10850/// would reach. The alias declarations the reference's own guards admit are
10851/// read directly: conditional arms that select different owners leave the
10852/// member undecided, and only arms that all agree decide it.
10853fn select_alias_qualifier_owner(
10854    node: Node<'_>,
10855    components: &[Node<'_>],
10856    global: bool,
10857    analyzer: &CppGraphSource<'_>,
10858    visibility: &VisibilityIndex<'_>,
10859    file: &ProjectFile,
10860    source: &str,
10861) -> QualifierOwnerSelection {
10862    let Some((terminal, qualifiers)) = components.split_last() else {
10863        return QualifierOwnerSelection::Unchanged;
10864    };
10865    let Some(last_qualifier) = qualifiers.last() else {
10866        return QualifierOwnerSelection::Unchanged;
10867    };
10868    let names = qualifiers
10869        .iter()
10870        .map(|component| node_text(*component, source).to_owned())
10871        .collect::<Vec<_>>();
10872    let declared = visibility
10873        .visible_identifier_candidates(file, node_text(*last_qualifier, source))
10874        .filter(|candidate| declared_type_alias(analyzer, candidate))
10875        .filter(|candidate| {
10876            let scope = canonical_cpp_scope_components(candidate);
10877            if global {
10878                scope == names
10879            } else {
10880                scope.ends_with(&names)
10881            }
10882        })
10883        .collect::<Vec<_>>();
10884    let aliases = declared
10885        .iter()
10886        .filter(|candidate| {
10887            visibility.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10888        })
10889        .map(|alias| visibility.canonical_type_unit_in_context(analyzer, file, node, alias))
10890        .collect::<Vec<_>>();
10891    let Some((first, rest)) = aliases.split_first() else {
10892        return QualifierOwnerSelection::Unchanged;
10893    };
10894    let Some(owner) = first else {
10895        return QualifierOwnerSelection::Unchanged;
10896    };
10897    if !rest.iter().all(|target| {
10898        target
10899            .as_ref()
10900            .is_some_and(|target| same_visible_symbol(target, owner))
10901    }) {
10902        return QualifierOwnerSelection::Ambiguous;
10903    }
10904    // Ordinary lookup already follows an unconditional alias to a class it
10905    // can name by spelling. It cannot name a specialization, and conditional
10906    // arms leave it undecided even when the arms the reference admits agree,
10907    // so the decided owner is used in those two cases only.
10908    let conditional = aliases.len() < declared.len();
10909    if !owner.is_class() || (!conditional && !visibility.is_template_specialization(owner)) {
10910        return QualifierOwnerSelection::Unchanged;
10911    }
10912    let mut redirected = canonical_cpp_scope_components(owner);
10913    redirected.push(node_text(*terminal, source).to_owned());
10914    QualifierOwnerSelection::Selected(redirected)
10915}
10916
10917pub fn resolve_type_node_lexically(
10918    node: Node<'_>,
10919    analyzer: &CppGraphSource<'_>,
10920    visibility: &VisibilityIndex<'_>,
10921    ordinary_type_imports: &OrdinaryTypeImportCell,
10922    file: &ProjectFile,
10923    source: &str,
10924) -> LexicalTypeResolution {
10925    let Some((components, global)) = type_reference_components(node, source) else {
10926        return LexicalTypeResolution::Missing;
10927    };
10928    match select_qualifier_owner(
10929        node,
10930        global,
10931        analyzer,
10932        visibility,
10933        ordinary_type_imports,
10934        file,
10935        source,
10936    ) {
10937        QualifierOwnerSelection::Unchanged => {}
10938        QualifierOwnerSelection::Selected(components) => {
10939            return resolve_type_components_lexically_at(
10940                node,
10941                &components,
10942                true,
10943                analyzer,
10944                visibility,
10945                ordinary_type_imports,
10946                file,
10947                source,
10948            );
10949        }
10950        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
10951    }
10952    let resolution = resolve_type_components_lexically_at(
10953        node,
10954        &components,
10955        global,
10956        analyzer,
10957        visibility,
10958        ordinary_type_imports,
10959        file,
10960        source,
10961    );
10962    if !is_cpp_template_argument_type_leaf(node) {
10963        return resolution;
10964    }
10965
10966    // Error recovery can detach a member function from its class while
10967    // leaving an unqualified type argument (for example `error_type` in
10968    // `expected<..., error_type>`). The normal structural scope then lacks
10969    // the class owner and resolves the wrong same-spelled alias, or fails
10970    // closed. The indexed enclosing unit still carries the authoritative
10971    // class scope; retry only this narrowly-shaped leaf with that scope.
10972    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10973        return resolution;
10974    };
10975    let namespace_scope = enclosing_namespace_components(node, source);
10976    if indexed_scope.len() <= namespace_scope.len() {
10977        return resolution;
10978    }
10979    let indexed = resolve_type_components_in_authoritative_scope(
10980        node,
10981        &components,
10982        global,
10983        analyzer,
10984        visibility,
10985        ordinary_type_imports,
10986        file,
10987        source,
10988        None,
10989        false,
10990        false,
10991        false,
10992        indexed_scope,
10993    );
10994    match indexed {
10995        LexicalTypeResolution::Resolved { ref unit, .. }
10996            if !visibility
10997                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10998        {
10999            resolution
11000        }
11001        LexicalTypeResolution::Resolved { .. } => indexed,
11002        _ => resolution,
11003    }
11004}
11005
11006/// Resolve a type lexically, using the target to guide incomplete evidence.
11007/// A lexical resolution visible in context wins over target guidance even when
11008/// its unit and candidates do not include the target.
11009#[allow(clippy::too_many_arguments)]
11010pub fn resolve_type_node_lexically_for_target(
11011    node: Node<'_>,
11012    analyzer: &CppGraphSource<'_>,
11013    visibility: &VisibilityIndex<'_>,
11014    ordinary_type_imports: &OrdinaryTypeImportCell,
11015    file: &ProjectFile,
11016    source: &str,
11017    target: &CodeUnit,
11018    scope_cache: Option<&LexicalScopeCache>,
11019    recovered_scope: Option<&[String]>,
11020) -> LexicalTypeResolution {
11021    let Some((reference_components, global)) = type_reference_components(node, source) else {
11022        return LexicalTypeResolution::Missing;
11023    };
11024    let terminal = reference_components
11025        .last()
11026        .expect("type reference components are non-empty");
11027    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
11028        return LexicalTypeResolution::Missing;
11029    }
11030    let template_arguments = cpp_template_reference_arguments(node, source);
11031    let selects_concrete_specialization =
11032        template_arguments.is_some() && visibility.is_template_specialization(target);
11033    if !selects_concrete_specialization
11034        && !visibility.structured_type_reference_may_resolve_to_target(
11035            analyzer,
11036            file,
11037            std::slice::from_ref(terminal),
11038            false,
11039            &[],
11040            target,
11041        )
11042    {
11043        return LexicalTypeResolution::Missing;
11044    }
11045    if let Some(arguments) = template_arguments.as_ref() {
11046        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
11047            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
11048                node,
11049                &reference_components,
11050                global,
11051                analyzer,
11052                visibility,
11053                ordinary_type_imports,
11054                file,
11055                source,
11056                recovered_scope,
11057            )
11058        } else {
11059            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
11060                node,
11061                &reference_components,
11062                global,
11063                analyzer,
11064                visibility,
11065                ordinary_type_imports,
11066                file,
11067                source,
11068                scope_cache,
11069            )
11070        };
11071        return match alias_resolution {
11072            LexicalTypeResolution::Resolved {
11073                unit,
11074                components,
11075                candidates,
11076            } if visibility.template_alias_arguments_preserve_target(
11077                analyzer, file, &unit, arguments, target,
11078            ) =>
11079            {
11080                LexicalTypeResolution::Resolved {
11081                    unit: target.clone(),
11082                    components,
11083                    candidates,
11084                }
11085            }
11086            LexicalTypeResolution::Resolved {
11087                unit,
11088                components,
11089                candidates,
11090            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
11091                Ok(resolved_unit) => {
11092                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
11093                        .then(|| {
11094                            target_guided_malformed_template_alias_resolution(
11095                                node,
11096                                analyzer,
11097                                visibility,
11098                                file,
11099                                arguments,
11100                                &reference_components,
11101                                target,
11102                            )
11103                        })
11104                        .flatten();
11105                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
11106                        unit: resolved_unit,
11107                        components,
11108                        candidates,
11109                    })
11110                }
11111                Err(_) => LexicalTypeResolution::Ambiguous,
11112            },
11113            LexicalTypeResolution::Missing => {
11114                let target_preserving = if let Some(recovered_scope) = recovered_scope {
11115                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
11116                        node,
11117                        &reference_components,
11118                        global,
11119                        analyzer,
11120                        visibility,
11121                        ordinary_type_imports,
11122                        file,
11123                        source,
11124                        target,
11125                        true,
11126                        recovered_scope,
11127                    )
11128                } else {
11129                    resolve_type_components_lexically_at_for_target_with_scope_cache(
11130                        node,
11131                        &reference_components,
11132                        global,
11133                        analyzer,
11134                        visibility,
11135                        ordinary_type_imports,
11136                        file,
11137                        source,
11138                        target,
11139                        true,
11140                        scope_cache,
11141                    )
11142                };
11143                match target_preserving {
11144                    LexicalTypeResolution::Resolved {
11145                        unit: _,
11146                        components,
11147                        candidates,
11148                    } if template_reference_candidates_select_target(
11149                        node,
11150                        &candidates,
11151                        analyzer,
11152                        visibility,
11153                        file,
11154                        source,
11155                        target,
11156                    ) =>
11157                    {
11158                        LexicalTypeResolution::Resolved {
11159                            unit: target.clone(),
11160                            components,
11161                            candidates,
11162                        }
11163                    }
11164                    _ => target_guided_malformed_template_alias_resolution(
11165                        node,
11166                        analyzer,
11167                        visibility,
11168                        file,
11169                        arguments,
11170                        &reference_components,
11171                        target,
11172                    )
11173                    .unwrap_or(LexicalTypeResolution::Missing),
11174                }
11175            }
11176            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
11177        };
11178    }
11179    match select_qualifier_owner(
11180        node,
11181        global,
11182        analyzer,
11183        visibility,
11184        ordinary_type_imports,
11185        file,
11186        source,
11187    ) {
11188        QualifierOwnerSelection::Unchanged => {}
11189        QualifierOwnerSelection::Selected(components) => {
11190            return resolve_type_components_lexically_at_for_target_with_scope_cache(
11191                node,
11192                &components,
11193                true,
11194                analyzer,
11195                visibility,
11196                ordinary_type_imports,
11197                file,
11198                source,
11199                target,
11200                true,
11201                scope_cache,
11202            );
11203        }
11204        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
11205    }
11206    let resolution = if let Some(recovered_scope) = recovered_scope {
11207        resolve_type_components_lexically_at_for_target_with_recovered_scope(
11208            node,
11209            &reference_components,
11210            global,
11211            analyzer,
11212            visibility,
11213            ordinary_type_imports,
11214            file,
11215            source,
11216            target,
11217            true,
11218            recovered_scope,
11219        )
11220    } else {
11221        resolve_type_components_lexically_at_for_target_with_scope_cache(
11222            node,
11223            &reference_components,
11224            global,
11225            analyzer,
11226            visibility,
11227            ordinary_type_imports,
11228            file,
11229            source,
11230            target,
11231            true,
11232            scope_cache,
11233        )
11234    };
11235    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
11236        target_guided_qualified_namespace_function_type_resolution(
11237            node,
11238            &reference_components,
11239            global,
11240            analyzer,
11241            visibility,
11242            ordinary_type_imports,
11243            file,
11244            source,
11245            target,
11246        )
11247        .unwrap_or(resolution)
11248    } else {
11249        resolution
11250    };
11251    if !is_cpp_template_argument_type_leaf(node) {
11252        return resolution;
11253    }
11254
11255    // Preprocessor recovery can lift a member declaration out of its class
11256    // field list.  The unqualified template argument is then resolved from
11257    // the namespace only, even though the indexed enclosing callable still
11258    // identifies the class owner.  Retry this exact leaf against that
11259    // structured owner scope; ordinary type nodes must continue to use the
11260    // parser-derived lexical scope so unrelated same-spelled aliases remain
11261    // excluded.
11262    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
11263        return resolution;
11264    };
11265    let namespace_scope = enclosing_namespace_components(node, source);
11266    if indexed_scope.len() <= namespace_scope.len() {
11267        return resolution;
11268    }
11269    let indexed = visibility.resolve_type_components_lexically_for_target(
11270        analyzer,
11271        file,
11272        &reference_components,
11273        global,
11274        &indexed_scope,
11275        target,
11276    );
11277    match indexed {
11278        LexicalTypeResolution::Resolved { ref unit, .. }
11279            if visibility
11280                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11281        {
11282            indexed
11283        }
11284        _ => resolution,
11285    }
11286}
11287
11288/// Recover the lexical namespace of an out-of-line namespace function whose
11289/// relative qualifier is reachable through a `using namespace` directive.
11290///
11291/// Per-file declaration extraction can attach `void schema::consume(...)` to
11292/// the first visible namespace prefix when more than one using-directive is
11293/// active. A matching visible free-function declaration still proves the
11294/// complete namespace. Target-guided inverse lookup may use that namespace to
11295/// retry a parameter or body type, but only when the target lives in the same
11296/// namespace and the declaration's callable arity matches the definition.
11297#[allow(clippy::too_many_arguments)]
11298fn target_guided_qualified_namespace_function_type_resolution(
11299    node: Node<'_>,
11300    components: &[String],
11301    global: bool,
11302    analyzer: &CppGraphSource<'_>,
11303    visibility: &VisibilityIndex<'_>,
11304    ordinary_type_imports: &OrdinaryTypeImportCell,
11305    file: &ProjectFile,
11306    source: &str,
11307    target: &CodeUnit,
11308) -> Option<LexicalTypeResolution> {
11309    let function_definition = std::iter::successors(Some(node), |current| current.parent())
11310        .find(|current| current.kind() == "function_definition")?;
11311    let function = function_definition_name_node(function_definition)?;
11312    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
11313    let target_namespace = target.package_name();
11314    if target_namespace.is_empty() {
11315        return None;
11316    }
11317    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11318        brokk_bifrost_core::analyzer::Language::Cpp,
11319        target_namespace,
11320    );
11321    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
11322    {
11323        return None;
11324    }
11325
11326    let function_name = node_text(function_terminal_node(function), source);
11327    let definition_unit = canonical_callable_at_node(
11328        function_definition,
11329        analyzer,
11330        visibility,
11331        file,
11332        source,
11333        TargetKind::FreeFunction,
11334    )?;
11335    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
11336    let declaration_proves_namespace = visibility
11337        .visible_identifier_candidates(file, function_name)
11338        .filter(|candidate| {
11339            candidate.is_function()
11340                && type_owner_of(analyzer, candidate).is_none()
11341                && candidate.package_name() == target_namespace
11342        })
11343        .any(|candidate| {
11344            cpp_callable_arity(analyzer, candidate)
11345                .is_none_or(|known| known.accepts(definition_arity))
11346        });
11347    if !declaration_proves_namespace {
11348        return None;
11349    }
11350
11351    let resolution = resolve_type_components_lexically_at_scoped(
11352        node,
11353        components,
11354        global,
11355        analyzer,
11356        visibility,
11357        ordinary_type_imports,
11358        file,
11359        source,
11360        Some(target),
11361        true,
11362        false,
11363        false,
11364        target_scope,
11365    );
11366    match &resolution {
11367        LexicalTypeResolution::Resolved {
11368            unit, candidates, ..
11369        } if (same_visible_symbol(unit, target)
11370            || candidates
11371                .iter()
11372                .any(|candidate| same_visible_symbol(candidate, target)))
11373            && visibility
11374                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11375        {
11376            Some(resolution)
11377        }
11378        LexicalTypeResolution::Resolved { .. }
11379        | LexicalTypeResolution::Ambiguous
11380        | LexicalTypeResolution::Missing => None,
11381    }
11382}
11383
11384#[allow(clippy::too_many_arguments)]
11385fn target_guided_malformed_template_alias_resolution(
11386    node: Node<'_>,
11387    analyzer: &CppGraphSource<'_>,
11388    visibility: &VisibilityIndex<'_>,
11389    file: &ProjectFile,
11390    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
11391    components: &[String],
11392    target: &CodeUnit,
11393) -> Option<LexicalTypeResolution> {
11394    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
11395        return None;
11396    }
11397
11398    let identifier = &components[0];
11399    let namespace =
11400        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
11401    let namespace_name = namespace.join("::");
11402    let candidates = visibility
11403        .visible_identifier_candidates(file, identifier)
11404        .filter(|candidate| {
11405            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
11406                && visibility.type_candidate_may_be_visible_before_reference(
11407                    analyzer,
11408                    file,
11409                    candidate,
11410                    node.start_byte(),
11411                )
11412        })
11413        .cloned()
11414        .collect::<Vec<_>>();
11415    let first = candidates.first()?;
11416    if !candidates
11417        .iter()
11418        .all(|candidate| same_logical_symbol(first, candidate))
11419        || !candidates.iter().all(|candidate| {
11420            visibility.template_alias_arguments_preserve_target(
11421                analyzer, file, candidate, arguments, target,
11422            )
11423        })
11424    {
11425        return None;
11426    }
11427
11428    let mut resolved_components = namespace;
11429    resolved_components.push(identifier.clone());
11430    Some(LexicalTypeResolution::Resolved {
11431        unit: target.clone(),
11432        components: resolved_components,
11433        candidates,
11434    })
11435}
11436
11437fn resolve_type_node_lexically_for_target_without_visibility(
11438    node: Node<'_>,
11439    analyzer: &CppGraphSource<'_>,
11440    visibility: &VisibilityIndex<'_>,
11441    file: &ProjectFile,
11442    source: &str,
11443    target: &CodeUnit,
11444) -> LexicalTypeResolution {
11445    let Some((components, global)) = type_reference_components(node, source) else {
11446        return LexicalTypeResolution::Missing;
11447    };
11448    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
11449        node,
11450        analyzer,
11451        visibility,
11452        file,
11453        source,
11454        true,
11455        recovered_macro_decorated_declarator_type(node)
11456            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11457        &orphaned_namespace_scopes(visibility, file),
11458    ) {
11459        LexicalScopeResolution::Resolved(scope) => scope,
11460        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11461        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11462    };
11463    visibility.resolve_type_components_lexically_for_target(
11464        analyzer,
11465        file,
11466        &components,
11467        global,
11468        &lexical_scope,
11469        target,
11470    )
11471}
11472
11473fn type_node_has_exact_target_identity_without_visibility(
11474    node: Node<'_>,
11475    analyzer: &CppGraphSource<'_>,
11476    visibility: &VisibilityIndex<'_>,
11477    file: &ProjectFile,
11478    source: &str,
11479    target: &CodeUnit,
11480) -> bool {
11481    let Some((components, global)) = type_reference_components(node, source) else {
11482        return false;
11483    };
11484    let LexicalScopeResolution::Resolved(lexical_scope) =
11485        enclosing_lexical_scope_components_with_unresolved_owner(
11486            node,
11487            analyzer,
11488            visibility,
11489            file,
11490            source,
11491            true,
11492            recovered_macro_decorated_declarator_type(node)
11493                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11494            &orphaned_namespace_scopes(visibility, file),
11495        )
11496    else {
11497        return false;
11498    };
11499    let target_name = cpp_name_for(target);
11500    lexical_component_tiers(&components, global, &lexical_scope)
11501        .any(|qualified| qualified.join("::") == target_name)
11502}
11503
11504pub fn resolve_using_enum_declaration_owner(
11505    node: Node<'_>,
11506    analyzer: &CppGraphSource<'_>,
11507    visibility: &VisibilityIndex<'_>,
11508    ordinary_type_imports: &OrdinaryTypeImportCell,
11509    file: &ProjectFile,
11510    source: &str,
11511) -> LexicalTypeResolution {
11512    let Some(type_node) = using_enum_declaration_type_node(node) else {
11513        return LexicalTypeResolution::Missing;
11514    };
11515    let mut components = Vec::new();
11516    if append_cpp_name_components(type_node, source, &mut components).is_none()
11517        || components.is_empty()
11518    {
11519        return LexicalTypeResolution::Missing;
11520    }
11521    resolve_type_components_lexically_at(
11522        type_node,
11523        &components,
11524        is_globally_qualified_cpp_name(type_node),
11525        analyzer,
11526        visibility,
11527        ordinary_type_imports,
11528        file,
11529        source,
11530    )
11531}
11532
11533pub fn resolve_ordinary_using_declaration_owner(
11534    node: Node<'_>,
11535    analyzer: &CppGraphSource<'_>,
11536    visibility: &VisibilityIndex<'_>,
11537    file: &ProjectFile,
11538    source: &str,
11539) -> LexicalTypeResolution {
11540    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
11541        return LexicalTypeResolution::Missing;
11542    };
11543    let mut components = Vec::new();
11544    if append_cpp_name_components(type_node, source, &mut components).is_none()
11545        || components.len() < 2
11546    {
11547        return LexicalTypeResolution::Missing;
11548    }
11549    let lexical_scope =
11550        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
11551            LexicalScopeResolution::Resolved(scope) => scope,
11552            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11553            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11554        };
11555    visibility.resolve_type_components_lexically(
11556        analyzer,
11557        file,
11558        &components,
11559        is_globally_qualified_cpp_name(type_node),
11560        &lexical_scope,
11561    )
11562}
11563
11564pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11565    (node.kind() == "using_declaration"
11566        && (0..node.child_count()).any(|index| {
11567            node.child(index)
11568                .is_some_and(|child| child.kind() == "enum")
11569        }))
11570    .then(|| node.named_child(0))
11571    .flatten()
11572}
11573
11574pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11575    (node.kind() == "using_declaration"
11576        && using_enum_declaration_type_node(node).is_none()
11577        && using_namespace_directive_name_node(node).is_none())
11578    .then(|| node.named_child(0))
11579    .flatten()
11580}
11581
11582/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
11583/// undefined namespace-sentinel macro as a declaration whose type is the
11584/// all-caps sentinel and whose qualified declarator starts with a pseudo
11585/// `using` scope. The real imported name remains a structured qualified
11586/// identifier under that declarator. Recover only this exact CST envelope so
11587/// ordinary macro-decorated variables are not treated as imports.
11588fn recovered_macro_using_declaration_type_node<'tree>(
11589    node: Node<'tree>,
11590    source: &str,
11591) -> Option<(Node<'tree>, bool)> {
11592    if node.kind() != "declaration" {
11593        return None;
11594    }
11595    let macro_type = node.child_by_field_name("type")?;
11596    if macro_type.kind() != "type_identifier"
11597        || !cpp_export_macro_token(node_text(macro_type, source))
11598    {
11599        return None;
11600    }
11601    let declarator = node.child_by_field_name("declarator")?;
11602    if declarator.kind() != "qualified_identifier" {
11603        return None;
11604    }
11605    let scope = declarator.child_by_field_name("scope")?;
11606    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
11607        return None;
11608    }
11609    let target = declarator.child_by_field_name("name")?;
11610    let mut components = Vec::new();
11611    append_cpp_name_components(target, source, &mut components)?;
11612    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
11613}
11614
11615fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11616    let is_directive = node.kind() == "using_directive"
11617        || (node.kind() == "using_declaration"
11618            && (0..node.child_count()).any(|index| {
11619                node.child(index)
11620                    .is_some_and(|child| child.kind() == "namespace")
11621            }));
11622    if !is_directive {
11623        return None;
11624    }
11625    node.child_by_field_name("name")
11626        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11627}
11628
11629fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11630    let mut current = node.parent();
11631    while let Some(parent) = current {
11632        if matches!(
11633            parent.kind(),
11634            "compound_statement"
11635                | "function_definition"
11636                | "lambda_expression"
11637                | "for_statement"
11638                | "while_statement"
11639                | "if_statement"
11640                | "class_specifier"
11641                | "struct_specifier"
11642                | "union_specifier"
11643        ) {
11644            return None;
11645        }
11646        current = parent.parent();
11647    }
11648    Some(enclosing_namespace_components(node, source))
11649}
11650
11651fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11652    let mut current = node.parent();
11653    while let Some(scope) = current {
11654        if matches!(
11655            scope.kind(),
11656            "compound_statement"
11657                | "declaration_list"
11658                | "field_declaration_list"
11659                | "translation_unit"
11660        ) {
11661            let mut depth = 0;
11662            let mut ancestor = scope.parent();
11663            while let Some(parent) = ancestor {
11664                depth += 1;
11665                ancestor = parent.parent();
11666            }
11667            return Some((
11668                scope.start_byte(),
11669                scope.end_byte(),
11670                depth,
11671                scope.kind() == "compound_statement",
11672            ));
11673        }
11674        current = scope.parent();
11675    }
11676    None
11677}
11678
11679/// Build the per-file structured using index for `file`.
11680///
11681/// The result is a pure function of the file's parsed content, which is what
11682/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11683/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11684/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11685pub fn build_source_using_index(
11686    cpp: &dyn CppSource,
11687    token: QueryToken<'_>,
11688    file: &ProjectFile,
11689) -> SourceUsingIndex {
11690    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11691        return SourceUsingIndex::default();
11692    };
11693    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11694}
11695
11696fn collect_source_using_index(
11697    cpp: &dyn CppSource,
11698    source_file: &ProjectFile,
11699    root: Node<'_>,
11700    source: &str,
11701) -> SourceUsingIndex {
11702    #[cfg(not(any(test, feature = "test-support")))]
11703    let _ = cpp;
11704    let mut index = SourceUsingIndex::default();
11705    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11706    let mut stack = vec![root];
11707    while let Some(node) = stack.pop() {
11708        let target = match node.kind() {
11709            "using_directive" | "using_declaration" => {
11710                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11711                    let mut namespace_components = Vec::new();
11712                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11713                        .map(|_| EffectiveUsingTarget::Namespace {
11714                            namespace_components,
11715                            global: is_globally_qualified_cpp_name(namespace_node),
11716                        })
11717                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11718                    let mut target_components = Vec::new();
11719                    (append_cpp_name_components(type_node, source, &mut target_components)
11720                        .is_some()
11721                        && target_components.len() >= 2)
11722                        .then(|| EffectiveUsingTarget::Ordinary {
11723                            name: target_components
11724                                .last()
11725                                .expect("ordinary using has a terminal component")
11726                                .clone(),
11727                            target_components,
11728                            global: is_globally_qualified_cpp_name(type_node),
11729                        })
11730                } else {
11731                    None
11732                }
11733            }
11734            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11735                |(type_node, global)| {
11736                    let mut target_components = Vec::new();
11737                    (append_cpp_name_components(type_node, source, &mut target_components)
11738                        .is_some()
11739                        && target_components.len() >= 2)
11740                        .then(|| EffectiveUsingTarget::Ordinary {
11741                            name: target_components
11742                                .last()
11743                                .expect("recovered ordinary using has a terminal component")
11744                                .clone(),
11745                            target_components,
11746                            global,
11747                        })
11748                },
11749            ),
11750            _ => None,
11751        };
11752        if let Some(target) = target {
11753            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11754            // searches from the root. The project index visits every AST node, but only these
11755            // structured using declarations need a guard environment. Keep the cheap target
11756            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11757            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11758            #[cfg(any(test, feature = "test-support"))]
11759            cpp.record_using_guard_context_inspection_for_test();
11760            let Some(required_guards) = binding_site_guards(node, source) else {
11761                let mut cursor = node.walk();
11762                stack.extend(node.children(&mut cursor));
11763                continue;
11764            };
11765            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11766                ordinary_using_scope(node)
11767            {
11768                let declaration_namespace = enclosing_namespace_components(node, source);
11769                let declaration_namespace = if declaration_namespace.is_empty() {
11770                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11771                        .unwrap_or(declaration_namespace)
11772                } else {
11773                    declaration_namespace
11774                };
11775                let namespace_scope = using_named_scope(node, source);
11776                let lexical_depth = declaration_namespace.len();
11777                let binding = OrdinaryTypeImport {
11778                    target,
11779                    source: source_file.clone(),
11780                    declaration_byte: node.end_byte(),
11781                    scope_start,
11782                    scope_end,
11783                    scope_depth,
11784                    block_scope,
11785                    lexical_depth,
11786                    declaration_namespace,
11787                    namespace_scope,
11788                    resolved_target_components: None,
11789                    required_guards,
11790                };
11791                match &binding.target {
11792                    EffectiveUsingTarget::Ordinary { name, .. } => index
11793                        .ordinary_by_name
11794                        .entry(name.clone())
11795                        .or_default()
11796                        .push(binding),
11797                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11798                }
11799            }
11800        }
11801        let mut cursor = node.walk();
11802        stack.extend(node.children(&mut cursor));
11803    }
11804    index
11805}
11806
11807struct OrphanedNamespaceEnvelope {
11808    body_end: usize,
11809    components: Vec<String>,
11810    class_names: HashSet<String>,
11811}
11812
11813/// Tree-sitter can terminate a namespace body at an object-like namespace
11814/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11815/// out-of-line definitions at translation-unit scope. A block-scoped using
11816/// declaration in one of those definitions still belongs to the namespace
11817/// selected by the malformed namespace envelope. Keep the envelope scan
11818/// source-local and reuse its structural ownership evidence for each using.
11819fn collect_orphaned_namespace_envelopes(
11820    root: Node<'_>,
11821    source: &str,
11822) -> Vec<OrphanedNamespaceEnvelope> {
11823    let mut envelopes = Vec::new();
11824    let mut stack = vec![root];
11825    while let Some(current) = stack.pop() {
11826        if current.kind() == "namespace_definition"
11827            && let Some(body) = current.child_by_field_name("body")
11828            && current.end_byte() == body.end_byte()
11829            && let Some(name) = current.child_by_field_name("name")
11830        {
11831            let mut components = enclosing_namespace_components(current, source);
11832            if append_cpp_name_components(name, source, &mut components).is_some()
11833                && !components.is_empty()
11834            {
11835                let mut class_names = HashSet::default();
11836                let mut body_stack = vec![body];
11837                while let Some(node) = body_stack.pop() {
11838                    if let Some(name) = orphaned_class_definition_name(node, source) {
11839                        class_names.insert(name);
11840                    }
11841                    let mut cursor = node.walk();
11842                    if node.kind() == "ERROR" {
11843                        body_stack.extend(node.children(&mut cursor));
11844                    } else {
11845                        body_stack.extend(node.named_children(&mut cursor));
11846                    }
11847                }
11848                envelopes.push(OrphanedNamespaceEnvelope {
11849                    body_end: body.end_byte(),
11850                    components,
11851                    class_names,
11852                });
11853            }
11854        }
11855        let mut cursor = current.walk();
11856        stack.extend(current.named_children(&mut cursor));
11857    }
11858    envelopes
11859}
11860
11861fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11862    if matches!(
11863        node.kind(),
11864        "class_specifier" | "struct_specifier" | "union_specifier"
11865    ) {
11866        let body = node.child_by_field_name("body")?;
11867        let name = node.child_by_field_name("name")?;
11868        return (!name.is_missing() && !body.is_missing())
11869            .then(|| node_text(name, source).to_string());
11870    }
11871    if node.kind() != "ERROR" {
11872        return None;
11873    }
11874
11875    // When an object-like namespace macro is parsed as a function definition,
11876    // tree-sitter can place the entire class declaration inside an ERROR node
11877    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11878    // fallback structural: accept only a named class-like keyword followed by
11879    // a real body, never an arbitrary identifier mentioned in the envelope.
11880    for index in 0..node.child_count() {
11881        let Some(keyword) = node.child(index) else {
11882            continue;
11883        };
11884        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11885            continue;
11886        }
11887        let mut name = None;
11888        for next_index in (index + 1)..node.child_count() {
11889            let Some(next) = node.child(next_index) else {
11890                continue;
11891            };
11892            if next.kind() == ";" {
11893                break;
11894            }
11895            if next.kind() == "{" {
11896                return name
11897                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11898                    .map(|name_node| node_text(name_node, source).to_string());
11899            }
11900            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11901                name = Some(next);
11902            }
11903        }
11904    }
11905    None
11906}
11907
11908fn recovered_orphaned_namespace_components(
11909    node: Node<'_>,
11910    source: &str,
11911    envelopes: &[OrphanedNamespaceEnvelope],
11912) -> Option<Vec<String>> {
11913    let owner_name = orphaned_using_owner_name(node, source)?;
11914    envelopes
11915        .iter()
11916        .filter(|envelope| {
11917            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11918        })
11919        .max_by_key(|envelope| envelope.body_end)
11920        .map(|envelope| envelope.components.clone())
11921}
11922
11923fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11924    let function = std::iter::successors(node.parent(), |current| current.parent())
11925        .find(|current| current.kind() == "function_definition")?;
11926    let owner = function_definition_owner_lookup_node(function)?;
11927    let scope = owner.child_by_field_name("scope")?;
11928    let mut components = Vec::new();
11929    append_cpp_name_components(scope, source, &mut components)?;
11930    // A qualified out-of-line member definition already carries its namespace
11931    // in the declarator scope (for example `foo::Widget::run`). The recovery
11932    // path is only for parser-orphaned top-level members whose owner scope
11933    // collapsed to the bare class name; requiring that shape prevents an
11934    // earlier, unrelated namespace/class from leaking into a global function's
11935    // using-directive lookup.
11936    if components.len() != 1 {
11937        return None;
11938    }
11939    components.pop()
11940}
11941
11942fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11943    let started = Instant::now();
11944    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11945    let source_files = visibility.all_visible_source_files();
11946    if report_stats {
11947        eprintln!(
11948            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11949            source_files.len()
11950        );
11951    }
11952    let mut project = ProjectUsingIndex::default();
11953    let mut ordinary_bindings = 0usize;
11954    for source_file in &source_files {
11955        // The per-file index is memoized on the analyzer, so assembling the
11956        // project index for a fresh `VisibilityIndex` copies bindings instead
11957        // of re-walking each file's AST (#1927).
11958        let source_index = visibility
11959            .cpp()
11960            .source_using_index(visibility.token(), source_file);
11961        for (name, bindings) in &source_index.ordinary_by_name {
11962            ordinary_bindings += bindings.len();
11963            project
11964                .ordinary_by_name
11965                .entry(name.clone())
11966                .or_default()
11967                .extend(bindings.iter().cloned());
11968        }
11969        project
11970            .directives
11971            .extend(source_index.directives.iter().cloned());
11972    }
11973    if report_stats {
11974        eprintln!(
11975            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11976            source_files.len(),
11977            project.ordinary_by_name.len(),
11978            ordinary_bindings,
11979            project.directives.len(),
11980            started.elapsed().as_millis(),
11981        );
11982    }
11983    project
11984}
11985
11986fn project_using_index(visibility: &VisibilityIndex<'_>) -> Arc<ProjectUsingIndex> {
11987    visibility.project_using_index(|| build_project_using_index(visibility))
11988}
11989
11990/// Build the immutable project-wide using index before a parallel file scan.
11991///
11992/// Keeping the `OnceLock` publication here avoids making every scanner carry
11993/// an eager index, while callers that are about to fan out can prevent one
11994/// worker from doing the whole build as its peers wait on the lock.
11995pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11996    let _ = project_using_index(visibility);
11997}
11998
11999fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
12000    let (components, global) = match &binding.target {
12001        EffectiveUsingTarget::Ordinary {
12002            target_components,
12003            global,
12004            ..
12005        } => (target_components, *global),
12006        EffectiveUsingTarget::Namespace {
12007            namespace_components,
12008            global,
12009        } => (namespace_components, *global),
12010    };
12011    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
12012}
12013
12014fn using_binding_target_components_for_name(
12015    binding: &OrdinaryTypeImport,
12016    project: &ProjectUsingIndex,
12017    visibility: &VisibilityIndex<'_>,
12018    file: &ProjectFile,
12019    name: &str,
12020) -> Option<Vec<String>> {
12021    // Built once per call rather than per candidate: the filter runs over every
12022    // visible identifier of `name`, and the source is the same object each time.
12023    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
12024    let visible_candidates = visibility
12025        .visible_identifier_candidates(file, name)
12026        .filter(|candidate| {
12027            candidate.is_class()
12028                || declared_type_alias(&cpp_source, candidate)
12029                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
12030        })
12031        .collect::<Vec<_>>();
12032    if visible_candidates.is_empty() {
12033        return None;
12034    }
12035    match &binding.target {
12036        EffectiveUsingTarget::Ordinary {
12037            name: imported_name,
12038            ..
12039        } if imported_name == name => {
12040            effective_using_target_tiers(binding)
12041                .into_iter()
12042                .find(|qualified| {
12043                    let qualified_name = qualified.join("::");
12044                    visible_candidates
12045                        .iter()
12046                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
12047                })
12048        }
12049        EffectiveUsingTarget::Namespace { .. } => {
12050            visibility.note_using_namespace_lookup_for_test();
12051            let target_tiers = effective_using_target_tiers(binding);
12052            let resolved = target_tiers
12053                .iter()
12054                .find(|namespace_components| {
12055                    let namespace = namespace_components.join("::");
12056                    visible_candidates.iter().any(|candidate| {
12057                        visibility.note_using_name_candidate_inspection_for_test();
12058                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
12059                            candidate_namespace == namespace
12060                                || candidate_namespace.starts_with(&format!("{namespace}::"))
12061                        })
12062                    }) || project.directives.iter().any(|candidate| {
12063                        candidate.namespace_scope.as_deref()
12064                            == Some(namespace_components.as_slice())
12065                    }) || project
12066                        .ordinary_by_name
12067                        .values()
12068                        .flatten()
12069                        .any(|candidate| {
12070                            candidate.namespace_scope.as_deref()
12071                                == Some(namespace_components.as_slice())
12072                        })
12073                })
12074                .cloned();
12075            resolved.or_else(|| {
12076                // A sole lexical namespace tier is itself enough to retain the
12077                // directive. Candidate identity is resolved later, where
12078                // target guidance and macro-expanded owner names are available.
12079                // Dropping it here makes an unrelated same-terminal type hide
12080                // the actual namespace member before lookup can compare owners.
12081                (target_tiers.len() == 1)
12082                    .then(|| target_tiers.into_iter().next())
12083                    .flatten()
12084            })
12085        }
12086        EffectiveUsingTarget::Ordinary { .. } => None,
12087    }
12088}
12089
12090fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
12091    let start = activation.checked_sub(1)?;
12092    let mut node = root.descendant_for_byte_range(start, activation)?;
12093    while node.kind() != "preproc_include" {
12094        node = node.parent()?;
12095    }
12096    Some(node)
12097}
12098
12099fn project_using_bindings(
12100    binding: OrdinaryTypeImport,
12101    visibility: &VisibilityIndex<'_>,
12102    file: &ProjectFile,
12103    root: Node<'_>,
12104    source: &str,
12105) -> Vec<OrdinaryTypeImport> {
12106    if binding.source == *file {
12107        return vec![binding];
12108    }
12109    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
12110        return Vec::new();
12111    }
12112    visibility.note_using_donor_activation_for_test();
12113    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
12114        return Vec::new();
12115    };
12116    let projections = visibility
12117        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
12118        .map_or_else(
12119            || {
12120                visibility.conditional_include_projections_for_source(
12121                    file,
12122                    prepared.as_ref(),
12123                    &binding.source,
12124                )
12125            },
12126            |activation_byte| {
12127                Arc::from([ConditionalIncludeProjection {
12128                    activation_byte,
12129                    required_guards: HashSet::default(),
12130                    partial_guards: HashSet::default(),
12131                }])
12132            },
12133        );
12134    projections
12135        .iter()
12136        .cloned()
12137        .filter_map(|projection| {
12138            let required_guards =
12139                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
12140            let mut projected = binding.clone();
12141            projected.required_guards = required_guards;
12142            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
12143        })
12144        .collect()
12145}
12146
12147fn project_using_binding_at_activation(
12148    mut binding: OrdinaryTypeImport,
12149    activation: usize,
12150    root: Node<'_>,
12151    source: &str,
12152) -> Option<OrdinaryTypeImport> {
12153    let include = include_node_for_activation(root, activation)?;
12154    let include_namespace = enclosing_namespace_components(include, source);
12155    let mut declaration_namespace = include_namespace.clone();
12156    declaration_namespace.extend(binding.declaration_namespace);
12157    binding.declaration_namespace = declaration_namespace;
12158    binding.declaration_byte = activation;
12159    if let Some(prefix) = using_named_scope(include, source) {
12160        let mut projected = prefix;
12161        projected.extend(binding.namespace_scope.take().unwrap_or_default());
12162        binding.scope_depth = projected.len();
12163        binding.block_scope = false;
12164        binding.lexical_depth = projected.len();
12165        binding.namespace_scope = Some(projected);
12166        binding.scope_start = 0;
12167        binding.scope_end = usize::MAX;
12168        Some(binding)
12169    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
12170        binding.namespace_scope = None;
12171        binding.scope_start = start;
12172        binding.scope_end = end;
12173        binding.scope_depth = depth;
12174        binding.block_scope = block_scope;
12175        binding.lexical_depth = include_namespace.len();
12176        Some(binding)
12177    } else {
12178        None
12179    }
12180}
12181
12182/// `node` may be any node of `file`'s tree; the projection reaches the tree
12183/// root itself when it needs one. `Node::parent` re-descends from the root on
12184/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
12185/// call site cost a near-full-AST scan per reference on a large flat file
12186/// (#1927); a name with no candidate bindings never pays for it.
12187pub fn effective_using_bindings_for_name(
12188    visibility: &VisibilityIndex<'_>,
12189    imports: &OrdinaryTypeImportCell,
12190    file: &ProjectFile,
12191    node: Node<'_>,
12192    source: &str,
12193    name: &str,
12194) -> Arc<[OrdinaryTypeImport]> {
12195    imports
12196        .projection_cell(name)
12197        .get_or_init(|| {
12198            let retained_project = project_using_index(visibility);
12199            let project = retained_project.as_ref();
12200            let name_bindings = project.ordinary_by_name.get(name);
12201            if name_bindings.is_none() && project.directives.is_empty() {
12202                return Arc::from(Vec::new());
12203            }
12204            let root = root_node(node);
12205            let mut projected = Vec::new();
12206            for binding in name_bindings
12207                .into_iter()
12208                .flatten()
12209                .chain(project.directives.iter())
12210            {
12211                if !visibility.source_is_visible(file, &binding.source) {
12212                    continue;
12213                }
12214                let target_components = using_binding_target_components_for_name(
12215                    binding, project, visibility, file, name,
12216                )
12217                .or_else(|| match &binding.target {
12218                    EffectiveUsingTarget::Ordinary {
12219                        name: imported_name,
12220                        target_components,
12221                        ..
12222                    } if imported_name == name => Some(target_components.clone()),
12223                    EffectiveUsingTarget::Ordinary { .. }
12224                    | EffectiveUsingTarget::Namespace { .. } => None,
12225                });
12226                let Some(target_components) = target_components else {
12227                    continue;
12228                };
12229                let mut binding = binding.clone();
12230                binding.resolved_target_components = Some(target_components);
12231                projected.extend(project_using_bindings(
12232                    binding, visibility, file, root, source,
12233                ));
12234            }
12235            Arc::from(projected)
12236        })
12237        .clone()
12238}
12239
12240pub fn initialized_ordinary_type_imports(
12241    root: Node<'_>,
12242    analyzer: &CppGraphSource<'_>,
12243    visibility: &VisibilityIndex<'_>,
12244    file: &ProjectFile,
12245    source: &str,
12246) -> OrdinaryTypeImportCell {
12247    let cell = visibility.ordinary_type_import_cell(file);
12248    let _ = (root, analyzer, source);
12249    cell
12250}
12251
12252/// `reference_guards` is the reference node's guard environment, computed once
12253/// by the caller and shared across every binding: recomputing it per binding
12254/// repeated a full ancestor climb whose every `Node::parent` step re-descends
12255/// from the root (#1927).
12256fn effective_using_binding_active(
12257    binding: &OrdinaryTypeImport,
12258    node: Node<'_>,
12259    lexical_scope: &[String],
12260    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12261    visibility: &VisibilityIndex<'_>,
12262    file: &ProjectFile,
12263) -> bool {
12264    effective_using_binding_guards_active(
12265        binding,
12266        node.start_byte(),
12267        reference_guards,
12268        visibility,
12269        file,
12270    ) && binding.namespace_scope.as_ref().map_or_else(
12271        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12272        |namespace| lexical_scope.starts_with(namespace),
12273    )
12274}
12275
12276fn effective_using_binding_guards_active(
12277    binding: &OrdinaryTypeImport,
12278    reference_byte: usize,
12279    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12280    visibility: &VisibilityIndex<'_>,
12281    file: &ProjectFile,
12282) -> bool {
12283    binding.declaration_byte <= reference_byte
12284        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
12285        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12286}
12287
12288fn effective_using_binding_guards_compatible(
12289    binding: &OrdinaryTypeImport,
12290    reference_byte: usize,
12291    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12292    visibility: &VisibilityIndex<'_>,
12293    file: &ProjectFile,
12294) -> bool {
12295    binding.source != *file
12296        && !binding.required_guards.is_empty()
12297        && binding.declaration_byte <= reference_byte
12298        && reference_guards.is_some_and(|active| {
12299            !binding.required_guards.is_subset(active)
12300                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
12301        })
12302        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12303}
12304
12305fn effective_using_binding_guards_stable_between(
12306    binding: &OrdinaryTypeImport,
12307    reference_byte: usize,
12308    visibility: &VisibilityIndex<'_>,
12309    file: &ProjectFile,
12310) -> bool {
12311    visibility.preprocessor_guards_stable_between(
12312        file,
12313        binding.declaration_byte,
12314        reference_byte,
12315        &binding.required_guards,
12316    )
12317}
12318
12319#[allow(clippy::too_many_arguments)]
12320fn binding_type_candidates(
12321    binding: &OrdinaryTypeImport,
12322    active_bindings: &[&OrdinaryTypeImport],
12323    analyzer: &CppGraphSource<'_>,
12324    visibility: &VisibilityIndex<'_>,
12325    file: &ProjectFile,
12326    name: &str,
12327    direct_target: Option<&CodeUnit>,
12328    reference_byte: usize,
12329) -> Vec<(CodeUnit, Vec<String>)> {
12330    ordinary_binding_lookup_targets(binding, active_bindings, name)
12331        .into_iter()
12332        .flat_map(|target| {
12333            let mut candidates = visibility
12334                .visible_identifier_candidates(file, name)
12335                .filter(|candidate| {
12336                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
12337                        && type_candidate_matches_lookup_components(
12338                            analyzer,
12339                            visibility,
12340                            file,
12341                            candidate,
12342                            reference_byte,
12343                            &target,
12344                        )
12345                })
12346                .cloned()
12347                .collect::<Vec<_>>();
12348            if candidates.is_empty()
12349                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12350                && let Some(target_unit) = direct_target
12351            {
12352                let expanded_target_name = macro_expanded_cpp_name_components(
12353                    visibility,
12354                    file,
12355                    target_unit,
12356                    reference_byte,
12357                );
12358                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
12359                    && expanded_target_name == target
12360                    && visibility.external_type_candidate_visible_at(
12361                        file,
12362                        target_unit,
12363                        reference_byte,
12364                    )
12365                {
12366                    candidates.push(target_unit.clone());
12367                }
12368            }
12369            if candidates.is_empty()
12370                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12371                && let Some(target_unit) = direct_target
12372            {
12373                let visible_types = visibility
12374                    .visible_identifier_candidates(file, name)
12375                    .filter(|candidate| {
12376                        candidate.is_class() || declared_type_alias(analyzer, candidate)
12377                    })
12378                    .collect::<Vec<_>>();
12379                let uniquely_names_target = !visible_types.is_empty()
12380                    && visible_types
12381                        .iter()
12382                        .all(|candidate| same_visible_symbol(candidate, target_unit));
12383                if uniquely_names_target {
12384                    candidates.extend(visible_types.into_iter().cloned());
12385                }
12386            }
12387            candidates
12388                .into_iter()
12389                .map(move |candidate| (candidate, target.clone()))
12390        })
12391        .collect()
12392}
12393
12394fn macro_expanded_cpp_name_components(
12395    visibility: &VisibilityIndex<'_>,
12396    file: &ProjectFile,
12397    unit: &CodeUnit,
12398    reference_byte: usize,
12399) -> Vec<String> {
12400    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12401        brokk_bifrost_core::analyzer::Language::Cpp,
12402        &cpp_name_for(unit),
12403    )
12404    .into_iter()
12405    .flat_map(|component| {
12406        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
12407    })
12408    .collect()
12409}
12410
12411fn macro_expanded_cpp_name_component(
12412    visibility: &VisibilityIndex<'_>,
12413    file: &ProjectFile,
12414    component: String,
12415    reference_byte: usize,
12416) -> Vec<String> {
12417    let Some(replacement) =
12418        visibility.object_macro_replacement_at(file, &component, reference_byte)
12419    else {
12420        return vec![component];
12421    };
12422    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12423        brokk_bifrost_core::analyzer::Language::Cpp,
12424        &replacement,
12425    );
12426    if expanded.is_empty() {
12427        vec![component]
12428    } else {
12429        expanded
12430    }
12431}
12432
12433/// Whether a visible type has the requested qualified spelling.
12434///
12435/// Members of an inline namespace are also members of its enclosing namespace,
12436/// so an ordinary using-declaration may legally omit the inline component. The
12437/// stored FQ name retains that component to keep declarations distinct. Recover
12438/// the omitted spellings from the candidate declaration's namespace ancestors,
12439/// using CST `inline` tokens rather than guessing from names.
12440fn type_candidate_matches_lookup_components(
12441    analyzer: &CppGraphSource<'_>,
12442    visibility: &VisibilityIndex<'_>,
12443    file: &ProjectFile,
12444    candidate: &CodeUnit,
12445    reference_byte: usize,
12446    target: &[String],
12447) -> bool {
12448    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
12449    if expanded == target {
12450        return true;
12451    }
12452    let Some(cpp) = analyzer.cpp else {
12453        return false;
12454    };
12455    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
12456        return false;
12457    };
12458    let root = prepared.tree().root_node();
12459    for range in analyzer.ranges(candidate) {
12460        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
12461        else {
12462            continue;
12463        };
12464        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
12465        loop {
12466            if current.kind() == "namespace_definition"
12467                && let Some(name) = current.child_by_field_name("name")
12468            {
12469                let mut components = Vec::new();
12470                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
12471                    && !components.is_empty()
12472                {
12473                    let inline = (0..current.child_count())
12474                        .filter_map(|index| current.child(index))
12475                        .any(|child| !child.is_named() && child.kind() == "inline");
12476                    namespaces.push((components, inline));
12477                }
12478            }
12479            let Some(parent) = current.parent() else {
12480                break;
12481            };
12482            current = parent;
12483        }
12484        namespaces.reverse();
12485        let mut namespace_components = Vec::new();
12486        let mut inline_indexes = HashSet::default();
12487        for (components, inline) in namespaces {
12488            for component in components {
12489                let expanded_component =
12490                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
12491                if inline {
12492                    inline_indexes.extend(
12493                        namespace_components.len()
12494                            ..namespace_components.len() + expanded_component.len(),
12495                    );
12496                }
12497                namespace_components.extend(expanded_component);
12498            }
12499        }
12500        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
12501            continue;
12502        }
12503        // Each inline namespace component can be present or elided. Compare
12504        // those alternatives as a small dynamic program instead of generating
12505        // every subset of a deeply nested inline-namespace chain.
12506        let mut reachable = vec![false; target.len() + 1];
12507        reachable[0] = true;
12508        for (index, component) in expanded.iter().enumerate() {
12509            let mut next = vec![false; target.len() + 1];
12510            for (target_index, reached) in reachable.iter().copied().enumerate() {
12511                if !reached {
12512                    continue;
12513                }
12514                if inline_indexes.contains(&index) {
12515                    next[target_index] = true;
12516                }
12517                if target
12518                    .get(target_index)
12519                    .is_some_and(|target_component| target_component == component)
12520                {
12521                    next[target_index + 1] = true;
12522                }
12523            }
12524            reachable = next;
12525        }
12526        if reachable[target.len()] {
12527            return true;
12528        }
12529    }
12530    false
12531}
12532
12533#[allow(clippy::too_many_arguments)]
12534fn resolved_type_import(
12535    candidates: Vec<(CodeUnit, Vec<String>)>,
12536    lexical_depth: usize,
12537    is_direct: bool,
12538    analyzer: &CppGraphSource<'_>,
12539    visibility: &VisibilityIndex<'_>,
12540    file: &ProjectFile,
12541    direct_target: Option<&CodeUnit>,
12542) -> OrdinaryTypeImportResolution {
12543    let logical = deduplicate_type_import_candidates(candidates);
12544    let selected = match logical.as_slice() {
12545        [] => return OrdinaryTypeImportResolution::Missing,
12546        [only] => only,
12547        // Several declarations of one FQN in one file are configuration
12548        // spellings of one entity, not competing types (#1845): the imported
12549        // name is unambiguous, only the branch that supplies it depends on the
12550        // build.
12551        several => {
12552            let units = several
12553                .iter()
12554                .map(|(unit, _)| unit)
12555                .collect::<Vec<&CodeUnit>>();
12556            let Some(spelling) = direct_target.and_then(|target| {
12557                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
12558            }) else {
12559                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
12560            };
12561            several
12562                .iter()
12563                .find(|(unit, _)| same_symbol(unit, spelling))
12564                .expect("the selected spelling is one of the imported candidates")
12565        }
12566    };
12567    OrdinaryTypeImportResolution::Resolved {
12568        target: selected.0.clone(),
12569        target_components: selected.1.clone(),
12570        lexical_depth,
12571        is_direct,
12572    }
12573}
12574
12575#[allow(clippy::too_many_arguments)]
12576fn ordinary_type_import_resolution(
12577    node: Node<'_>,
12578    components: &[String],
12579    global: bool,
12580    analyzer: &CppGraphSource<'_>,
12581    visibility: &VisibilityIndex<'_>,
12582    imports: &OrdinaryTypeImportCell,
12583    file: &ProjectFile,
12584    source: &str,
12585    lexical_scope: &[String],
12586    direct_target: Option<&CodeUnit>,
12587) -> OrdinaryTypeImportResolution {
12588    if global || components.len() != 1 {
12589        return OrdinaryTypeImportResolution::Missing;
12590    }
12591    let name = &components[0];
12592    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12593    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
12594    // step re-descends from the root (#1927). A name with no bindings needs
12595    // none of it, and one environment serves every binding of the reference.
12596    if bindings.is_empty() {
12597        return OrdinaryTypeImportResolution::Missing;
12598    }
12599    let reference_guards = preprocessor_guard_environment(node, source);
12600    let active = bindings
12601        .iter()
12602        .filter(|binding| {
12603            effective_using_binding_active(
12604                binding,
12605                node,
12606                lexical_scope,
12607                reference_guards.as_ref(),
12608                visibility,
12609                file,
12610            )
12611        })
12612        .collect::<Vec<_>>();
12613    let transitive = bindings
12614        .iter()
12615        .filter(|binding| {
12616            effective_using_binding_guards_active(
12617                binding,
12618                node.start_byte(),
12619                reference_guards.as_ref(),
12620                visibility,
12621                file,
12622            ) && (binding.namespace_scope.is_some()
12623                || (binding.scope_start <= node.start_byte()
12624                    && node.end_byte() <= binding.scope_end))
12625        })
12626        .collect::<Vec<_>>();
12627    ordinary_type_import_resolution_for_bindings(
12628        node,
12629        name,
12630        analyzer,
12631        visibility,
12632        file,
12633        lexical_scope,
12634        direct_target,
12635        &active,
12636        &transitive,
12637    )
12638}
12639
12640#[allow(clippy::too_many_arguments)]
12641fn compatible_foreign_type_import_resolution(
12642    node: Node<'_>,
12643    components: &[String],
12644    global: bool,
12645    analyzer: &CppGraphSource<'_>,
12646    visibility: &VisibilityIndex<'_>,
12647    imports: &OrdinaryTypeImportCell,
12648    file: &ProjectFile,
12649    source: &str,
12650    lexical_scope: &[String],
12651    direct_target: Option<&CodeUnit>,
12652) -> OrdinaryTypeImportResolution {
12653    if global || components.len() != 1 {
12654        return OrdinaryTypeImportResolution::Missing;
12655    }
12656    let name = &components[0];
12657    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12658    if bindings.is_empty() {
12659        return OrdinaryTypeImportResolution::Missing;
12660    }
12661    let reference_guards = preprocessor_guard_environment(node, source);
12662    let compatible = bindings
12663        .iter()
12664        .filter(|binding| {
12665            effective_using_binding_guards_compatible(
12666                binding,
12667                node.start_byte(),
12668                reference_guards.as_ref(),
12669                visibility,
12670                file,
12671            ) && binding.namespace_scope.as_ref().map_or_else(
12672                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12673                |namespace| lexical_scope.starts_with(namespace),
12674            )
12675        })
12676        .collect::<Vec<_>>();
12677    let transitive = bindings
12678        .iter()
12679        .filter(|binding| {
12680            effective_using_binding_guards_compatible(
12681                binding,
12682                node.start_byte(),
12683                reference_guards.as_ref(),
12684                visibility,
12685                file,
12686            ) && (binding.namespace_scope.is_some()
12687                || (binding.scope_start <= node.start_byte()
12688                    && node.end_byte() <= binding.scope_end))
12689        })
12690        .collect::<Vec<_>>();
12691    ordinary_type_import_resolution_for_bindings(
12692        node,
12693        name,
12694        analyzer,
12695        visibility,
12696        file,
12697        lexical_scope,
12698        direct_target,
12699        &compatible,
12700        &transitive,
12701    )
12702}
12703
12704#[allow(clippy::too_many_arguments)]
12705fn ordinary_type_import_resolution_for_bindings(
12706    node: Node<'_>,
12707    name: &str,
12708    analyzer: &CppGraphSource<'_>,
12709    visibility: &VisibilityIndex<'_>,
12710    file: &ProjectFile,
12711    lexical_scope: &[String],
12712    direct_target: Option<&CodeUnit>,
12713    active: &[&OrdinaryTypeImport],
12714    transitive: &[&OrdinaryTypeImport],
12715) -> OrdinaryTypeImportResolution {
12716    let mut concrete_depths = active
12717        .iter()
12718        .filter(|binding| binding.namespace_scope.is_none())
12719        .map(|binding| binding.scope_depth)
12720        .collect::<Vec<_>>();
12721    concrete_depths.sort_unstable();
12722    concrete_depths.dedup();
12723    for depth in concrete_depths.into_iter().rev() {
12724        let at_tier = active
12725            .iter()
12726            .copied()
12727            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12728        let direct = at_tier
12729            .clone()
12730            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12731            .flat_map(|binding| {
12732                binding_type_candidates(
12733                    binding,
12734                    transitive,
12735                    analyzer,
12736                    visibility,
12737                    file,
12738                    name,
12739                    direct_target,
12740                    node.start_byte(),
12741                )
12742            })
12743            .collect::<Vec<_>>();
12744        if !direct.is_empty() {
12745            return resolved_type_import(
12746                direct,
12747                lexical_scope.len(),
12748                true,
12749                analyzer,
12750                visibility,
12751                file,
12752                direct_target,
12753            );
12754        }
12755        let directives = at_tier
12756            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12757            .flat_map(|binding| {
12758                binding_type_candidates(
12759                    binding,
12760                    transitive,
12761                    analyzer,
12762                    visibility,
12763                    file,
12764                    name,
12765                    direct_target,
12766                    node.start_byte(),
12767                )
12768            })
12769            .collect::<Vec<_>>();
12770        if !directives.is_empty() {
12771            return resolved_type_import(
12772                directives,
12773                lexical_scope.len(),
12774                false,
12775                analyzer,
12776                visibility,
12777                file,
12778                direct_target,
12779            );
12780        }
12781    }
12782    for prefix_len in (0..=lexical_scope.len()).rev() {
12783        let tier = &lexical_scope[..prefix_len];
12784        let at_tier = active
12785            .iter()
12786            .copied()
12787            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12788        let direct = at_tier
12789            .clone()
12790            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12791            .flat_map(|binding| {
12792                binding_type_candidates(
12793                    binding,
12794                    transitive,
12795                    analyzer,
12796                    visibility,
12797                    file,
12798                    name,
12799                    direct_target,
12800                    node.start_byte(),
12801                )
12802            })
12803            .collect::<Vec<_>>();
12804        if !direct.is_empty() {
12805            return resolved_type_import(
12806                direct,
12807                prefix_len,
12808                true,
12809                analyzer,
12810                visibility,
12811                file,
12812                direct_target,
12813            );
12814        }
12815        let directives = at_tier
12816            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12817            .flat_map(|binding| {
12818                binding_type_candidates(
12819                    binding,
12820                    transitive,
12821                    analyzer,
12822                    visibility,
12823                    file,
12824                    name,
12825                    direct_target,
12826                    node.start_byte(),
12827                )
12828            })
12829            .collect::<Vec<_>>();
12830        if !directives.is_empty() {
12831            return resolved_type_import(
12832                directives,
12833                prefix_len,
12834                false,
12835                analyzer,
12836                visibility,
12837                file,
12838                direct_target,
12839            );
12840        }
12841    }
12842    OrdinaryTypeImportResolution::Missing
12843}
12844
12845#[allow(clippy::too_many_arguments)]
12846pub fn resolve_type_components_lexically_at(
12847    node: Node<'_>,
12848    components: &[String],
12849    global: bool,
12850    analyzer: &CppGraphSource<'_>,
12851    visibility: &VisibilityIndex<'_>,
12852    ordinary_type_imports: &OrdinaryTypeImportCell,
12853    file: &ProjectFile,
12854    source: &str,
12855) -> LexicalTypeResolution {
12856    resolve_type_components_lexically_at_inner(
12857        node,
12858        components,
12859        global,
12860        analyzer,
12861        visibility,
12862        ordinary_type_imports,
12863        file,
12864        source,
12865        None,
12866        false,
12867        false,
12868        false,
12869        None,
12870    )
12871}
12872
12873/// Resolve a type at its lexical reference site while retaining the identity
12874/// of an alias that C++ lookup selects.
12875///
12876/// Forward navigation uses the selected spelling as its destination, whereas
12877/// graph attribution normally canonicalizes an alias to its target. Both
12878/// surfaces must still apply the same ordinary using-declarations, declaration
12879/// order, guard state, and lexical-depth precedence.
12880#[allow(clippy::too_many_arguments)]
12881pub fn resolve_type_components_lexically_at_preserving_alias(
12882    node: Node<'_>,
12883    components: &[String],
12884    global: bool,
12885    analyzer: &CppGraphSource<'_>,
12886    visibility: &VisibilityIndex<'_>,
12887    file: &ProjectFile,
12888    source: &str,
12889) -> LexicalTypeResolution {
12890    let ordinary_type_imports =
12891        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12892    resolve_type_components_lexically_at_inner(
12893        node,
12894        components,
12895        global,
12896        analyzer,
12897        visibility,
12898        &ordinary_type_imports,
12899        file,
12900        source,
12901        None,
12902        false,
12903        true,
12904        true,
12905        None,
12906    )
12907}
12908
12909#[allow(clippy::too_many_arguments)]
12910fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12911    node: Node<'_>,
12912    components: &[String],
12913    global: bool,
12914    analyzer: &CppGraphSource<'_>,
12915    visibility: &VisibilityIndex<'_>,
12916    ordinary_type_imports: &OrdinaryTypeImportCell,
12917    file: &ProjectFile,
12918    source: &str,
12919    scope_cache: Option<&LexicalScopeCache>,
12920) -> LexicalTypeResolution {
12921    resolve_type_components_lexically_at_inner(
12922        node,
12923        components,
12924        global,
12925        analyzer,
12926        visibility,
12927        ordinary_type_imports,
12928        file,
12929        source,
12930        None,
12931        false,
12932        true,
12933        false,
12934        scope_cache,
12935    )
12936}
12937
12938#[allow(clippy::too_many_arguments)]
12939fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12940    node: Node<'_>,
12941    components: &[String],
12942    global: bool,
12943    analyzer: &CppGraphSource<'_>,
12944    visibility: &VisibilityIndex<'_>,
12945    ordinary_type_imports: &OrdinaryTypeImportCell,
12946    file: &ProjectFile,
12947    source: &str,
12948    target: &CodeUnit,
12949    apply_structured_prefilter: bool,
12950    scope_cache: Option<&LexicalScopeCache>,
12951) -> LexicalTypeResolution {
12952    resolve_type_components_lexically_at_inner(
12953        node,
12954        components,
12955        global,
12956        analyzer,
12957        visibility,
12958        ordinary_type_imports,
12959        file,
12960        source,
12961        Some(target),
12962        apply_structured_prefilter,
12963        false,
12964        false,
12965        scope_cache,
12966    )
12967}
12968
12969#[allow(clippy::too_many_arguments)]
12970fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12971    node: Node<'_>,
12972    components: &[String],
12973    global: bool,
12974    analyzer: &CppGraphSource<'_>,
12975    visibility: &VisibilityIndex<'_>,
12976    ordinary_type_imports: &OrdinaryTypeImportCell,
12977    file: &ProjectFile,
12978    source: &str,
12979    recovered_scope: &[String],
12980) -> LexicalTypeResolution {
12981    resolve_type_components_in_authoritative_scope(
12982        node,
12983        components,
12984        global,
12985        analyzer,
12986        visibility,
12987        ordinary_type_imports,
12988        file,
12989        source,
12990        None,
12991        false,
12992        true,
12993        false,
12994        recovered_scope.to_vec(),
12995    )
12996}
12997
12998#[allow(clippy::too_many_arguments)]
12999fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
13000    node: Node<'_>,
13001    components: &[String],
13002    global: bool,
13003    analyzer: &CppGraphSource<'_>,
13004    visibility: &VisibilityIndex<'_>,
13005    ordinary_type_imports: &OrdinaryTypeImportCell,
13006    file: &ProjectFile,
13007    source: &str,
13008    target: &CodeUnit,
13009    apply_structured_prefilter: bool,
13010    recovered_scope: &[String],
13011) -> LexicalTypeResolution {
13012    resolve_type_components_in_authoritative_scope(
13013        node,
13014        components,
13015        global,
13016        analyzer,
13017        visibility,
13018        ordinary_type_imports,
13019        file,
13020        source,
13021        Some(target),
13022        apply_structured_prefilter,
13023        false,
13024        false,
13025        recovered_scope.to_vec(),
13026    )
13027}
13028
13029#[allow(clippy::too_many_arguments)]
13030fn resolve_type_components_lexically_at_inner(
13031    node: Node<'_>,
13032    components: &[String],
13033    global: bool,
13034    analyzer: &CppGraphSource<'_>,
13035    visibility: &VisibilityIndex<'_>,
13036    ordinary_type_imports: &OrdinaryTypeImportCell,
13037    file: &ProjectFile,
13038    source: &str,
13039    direct_target: Option<&CodeUnit>,
13040    apply_structured_prefilter: bool,
13041    preserve_alias: bool,
13042    allow_compatible_foreign_import: bool,
13043    scope_cache: Option<&LexicalScopeCache>,
13044) -> LexicalTypeResolution {
13045    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13046    let lexical_scope_started = Instant::now();
13047    if report_stats {
13048        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
13049    }
13050    let lexical_scope = if global {
13051        Vec::new()
13052    } else {
13053        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
13054            node,
13055            analyzer,
13056            visibility,
13057            file,
13058            source,
13059            true,
13060            recovered_macro_decorated_declarator_type(node)
13061                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
13062            scope_cache,
13063        ) {
13064            LexicalScopeResolution::Resolved(scope) => scope,
13065            LexicalScopeResolution::Ambiguous => {
13066                if report_stats {
13067                    eprintln!(
13068                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
13069                        lexical_scope_started.elapsed().as_millis(),
13070                    );
13071                }
13072                return LexicalTypeResolution::Ambiguous;
13073            }
13074            LexicalScopeResolution::Missing => {
13075                if report_stats {
13076                    eprintln!(
13077                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
13078                        lexical_scope_started.elapsed().as_millis(),
13079                    );
13080                }
13081                return LexicalTypeResolution::Missing;
13082            }
13083        }
13084    };
13085    if report_stats {
13086        eprintln!(
13087            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
13088            lexical_scope.len(),
13089            lexical_scope_started.elapsed().as_millis(),
13090        );
13091    }
13092    resolve_type_components_lexically_at_scoped(
13093        node,
13094        components,
13095        global,
13096        analyzer,
13097        visibility,
13098        ordinary_type_imports,
13099        file,
13100        source,
13101        direct_target,
13102        apply_structured_prefilter,
13103        preserve_alias,
13104        allow_compatible_foreign_import,
13105        lexical_scope,
13106    )
13107}
13108
13109#[allow(clippy::too_many_arguments)]
13110fn resolve_type_components_lexically_at_scoped(
13111    node: Node<'_>,
13112    components: &[String],
13113    global: bool,
13114    analyzer: &CppGraphSource<'_>,
13115    visibility: &VisibilityIndex<'_>,
13116    ordinary_type_imports: &OrdinaryTypeImportCell,
13117    file: &ProjectFile,
13118    source: &str,
13119    direct_target: Option<&CodeUnit>,
13120    apply_structured_prefilter: bool,
13121    preserve_alias: bool,
13122    allow_compatible_foreign_import: bool,
13123    mut lexical_scope: Vec<String>,
13124) -> LexicalTypeResolution {
13125    if !global
13126        && components.len() == 1
13127        // The two constant-time conditions run before the ancestor climb: each
13128        // `Node::parent` step re-descends from the root (#1927).
13129        && let Some(target) = direct_target
13130        && lexical_scope
13131            .last()
13132            .is_none_or(|last| last != &components[0])
13133        // A recovered class may contain a real member function nested inside
13134        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
13135        // XMLConstHandle). The nearest function_definition is then the member
13136        // itself, so inspect the complete ancestor chain.
13137        && has_malformed_wrapper_function_definition_ancestor(node)
13138        && let Some(indexed_namespace) =
13139            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
13140        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
13141    {
13142        lexical_scope = indexed_namespace;
13143    }
13144    resolve_type_components_in_authoritative_scope(
13145        node,
13146        components,
13147        global,
13148        analyzer,
13149        visibility,
13150        ordinary_type_imports,
13151        file,
13152        source,
13153        direct_target,
13154        apply_structured_prefilter,
13155        preserve_alias,
13156        allow_compatible_foreign_import,
13157        lexical_scope,
13158    )
13159}
13160
13161/// Resolve within a scope already proven by recovered syntax.
13162///
13163/// Unlike parser-derived scope, this scope must not be replaced with the
13164/// queried target's namespace: doing so would let target guidance override a
13165/// nearer declaration represented by the recovered syntax.
13166///
13167/// A qualifier that names a namespace alias in effect at `node` is rebound
13168/// here, once, for forward and inverse lookup alike: the rebound name is
13169/// looked up from the scope of the alias that supplies it. An ordinary name
13170/// the written spelling reaches at a deeper lexical tier shadows the alias,
13171/// and an alias whose target cannot be established resolves to nothing.
13172#[allow(clippy::too_many_arguments)]
13173pub(super) fn resolve_type_components_in_authoritative_scope(
13174    node: Node<'_>,
13175    components: &[String],
13176    global: bool,
13177    analyzer: &CppGraphSource<'_>,
13178    visibility: &VisibilityIndex<'_>,
13179    ordinary_type_imports: &OrdinaryTypeImportCell,
13180    file: &ProjectFile,
13181    source: &str,
13182    direct_target: Option<&CodeUnit>,
13183    apply_structured_prefilter: bool,
13184    preserve_alias: bool,
13185    allow_compatible_foreign_import: bool,
13186    lexical_scope: Vec<String>,
13187) -> LexicalTypeResolution {
13188    // Block scope is the innermost scope of unqualified lookup, so a local
13189    // class answers before every namespace, member and import tier below.
13190    if !global
13191        && let Some(resolution) =
13192            function_local_type_resolution(analyzer, file, node, components, source)
13193    {
13194        return resolution;
13195    }
13196    let resolve = |components: &[String], global: bool, lexical_scope: Vec<String>| {
13197        resolve_unaliased_type_components_in_authoritative_scope(
13198            node,
13199            components,
13200            global,
13201            analyzer,
13202            visibility,
13203            ordinary_type_imports,
13204            file,
13205            source,
13206            direct_target,
13207            apply_structured_prefilter,
13208            preserve_alias,
13209            allow_compatible_foreign_import,
13210            lexical_scope,
13211        )
13212    };
13213    let binding =
13214        visibility.bind_namespace_qualifier(file, node, components, global, &lexical_scope);
13215    let Some(depth) = binding.depth() else {
13216        return resolve(components, global, lexical_scope);
13217    };
13218    if let LexicalTypeResolution::Resolved {
13219        components: resolved,
13220        ..
13221    } = visibility.resolve_type_components_lexically(
13222        analyzer,
13223        file,
13224        components,
13225        global,
13226        &lexical_scope,
13227    ) && resolved.len().saturating_sub(components.len()) > depth
13228    {
13229        return resolve(components, global, lexical_scope);
13230    }
13231    match binding {
13232        NamespaceQualifierBinding::Bound {
13233            components,
13234            global,
13235            lexical_scope,
13236            ..
13237        } => resolve(&components, global, lexical_scope),
13238        NamespaceQualifierBinding::Unresolved { .. } => LexicalTypeResolution::Ambiguous,
13239        NamespaceQualifierBinding::Unaliased => unreachable!("an unaliased binding has no depth"),
13240    }
13241}
13242
13243/// Select conditional alias arms with the reference's guards before
13244/// canonicalizing them (#6902).
13245///
13246/// A resolved answer carries the arms as its candidates. An ambiguity carries
13247/// none, so the alias-preserving lookup at the same scope recovers them. A
13248/// proven selection resolves to its one type; undecided arms stay ambiguous
13249/// rather than naming either class.
13250#[allow(clippy::too_many_arguments)]
13251fn guarded_alias_type_resolution(
13252    node: Node<'_>,
13253    components: &[String],
13254    global: bool,
13255    lexical_scope: &[String],
13256    resolution: LexicalTypeResolution,
13257    analyzer: &CppGraphSource<'_>,
13258    visibility: &VisibilityIndex<'_>,
13259    file: &ProjectFile,
13260) -> LexicalTypeResolution {
13261    let preserved;
13262    let arms = match &resolution {
13263        LexicalTypeResolution::Resolved { .. } => &resolution,
13264        LexicalTypeResolution::Ambiguous => {
13265            preserved = visibility.resolve_type_components_lexically_for_forward(
13266                analyzer,
13267                file,
13268                components,
13269                global,
13270                lexical_scope,
13271            );
13272            &preserved
13273        }
13274        LexicalTypeResolution::Missing => return resolution,
13275    };
13276    let LexicalTypeResolution::Resolved {
13277        components: resolved_components,
13278        candidates,
13279        ..
13280    } = arms
13281    else {
13282        return resolution;
13283    };
13284    if !candidates
13285        .iter()
13286        .any(|candidate| declared_type_alias(analyzer, candidate))
13287    {
13288        return resolution;
13289    }
13290    match visibility.guarded_alias_selection_at_reference(analyzer, file, node, candidates) {
13291        None => resolution,
13292        Some(GuardedAliasSelection::Proven { unit, candidates }) => {
13293            LexicalTypeResolution::Resolved {
13294                unit,
13295                components: resolved_components.clone(),
13296                candidates,
13297            }
13298        }
13299        Some(GuardedAliasSelection::Alternatives(_)) => LexicalTypeResolution::Ambiguous,
13300    }
13301}
13302
13303#[allow(clippy::too_many_arguments)]
13304fn resolve_unaliased_type_components_in_authoritative_scope(
13305    node: Node<'_>,
13306    components: &[String],
13307    global: bool,
13308    analyzer: &CppGraphSource<'_>,
13309    visibility: &VisibilityIndex<'_>,
13310    ordinary_type_imports: &OrdinaryTypeImportCell,
13311    file: &ProjectFile,
13312    source: &str,
13313    direct_target: Option<&CodeUnit>,
13314    apply_structured_prefilter: bool,
13315    preserve_alias: bool,
13316    allow_compatible_foreign_import: bool,
13317    lexical_scope: Vec<String>,
13318) -> LexicalTypeResolution {
13319    if apply_structured_prefilter
13320        && direct_target.is_some()
13321        && !preserve_alias
13322        && !global
13323        && components.len() == 1
13324        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
13325    {
13326        return LexicalTypeResolution::Missing;
13327    }
13328    // A recovered macro-prefixed return type can share its global spelling
13329    // with aliases from mutually exclusive included headers. C++ lookup uses
13330    // the declaration physically present earlier in this file; the visibility
13331    // index deliberately retains every configuration alternative. Restore
13332    // that precedence only for the exact macro-decorated head (recovered by
13333    // the older grammar, or its clean macro-declaration reading) and lexical
13334    // tier.
13335    if !global
13336        && components.len() == 1
13337        && (recovered_macro_decorated_type_node(node).is_some()
13338            || macro_modifier_decorated_type_node(node))
13339        && let Some(resolution) = recovered_same_file_type_alias_resolution(
13340            node,
13341            components,
13342            analyzer,
13343            visibility,
13344            file,
13345            direct_target,
13346            &lexical_scope,
13347        )
13348    {
13349        return resolution;
13350    }
13351    if apply_structured_prefilter
13352        && let Some(target) = direct_target
13353        && !preserve_alias
13354        && !visibility.structured_type_reference_may_resolve_to_target(
13355            analyzer,
13356            file,
13357            components,
13358            global,
13359            &lexical_scope,
13360            target,
13361        )
13362    {
13363        return LexicalTypeResolution::Missing;
13364    }
13365    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13366    let lexical_normal_started = Instant::now();
13367    if report_stats {
13368        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
13369    }
13370    let normal = if preserve_alias {
13371        visibility.resolve_type_components_lexically_for_forward(
13372            analyzer,
13373            file,
13374            components,
13375            global,
13376            &lexical_scope,
13377        )
13378    } else {
13379        direct_target.map_or_else(
13380            || {
13381                visibility.resolve_type_components_lexically(
13382                    analyzer,
13383                    file,
13384                    components,
13385                    global,
13386                    &lexical_scope,
13387                )
13388            },
13389            |target| {
13390                visibility.resolve_type_components_lexically_for_target(
13391                    analyzer,
13392                    file,
13393                    components,
13394                    global,
13395                    &lexical_scope,
13396                    target,
13397                )
13398            },
13399        )
13400    };
13401    if report_stats {
13402        eprintln!(
13403            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
13404            lexical_normal_started.elapsed().as_millis(),
13405        );
13406    }
13407    // Conditional alias arms face this reference's guards before their targets
13408    // are canonicalized together (#6902). The preserving forward mode keeps the
13409    // alias itself, so it has nothing to select.
13410    let normal = if preserve_alias || cpp_template_reference_arguments(node, source).is_some() {
13411        normal
13412    } else {
13413        guarded_alias_type_resolution(
13414            node,
13415            components,
13416            global,
13417            &lexical_scope,
13418            normal,
13419            analyzer,
13420            visibility,
13421            file,
13422        )
13423    };
13424    let normal = match normal {
13425        LexicalTypeResolution::Resolved { ref unit, .. }
13426            if !visibility
13427                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
13428                && !direct_target.is_some_and(|target| {
13429                    let candidate_refs = visibility
13430                        .visible_identifier_candidates(file, target.identifier())
13431                        .collect::<Vec<_>>();
13432                    visibility.c_tag_declaration_family_matches_target(
13433                        analyzer,
13434                        file,
13435                        &candidate_refs,
13436                        target,
13437                    )
13438                }) =>
13439        {
13440            LexicalTypeResolution::Missing
13441        }
13442        resolution => resolution,
13443    };
13444    let normal_depth = match &normal {
13445        LexicalTypeResolution::Resolved { components, .. } => {
13446            Some(components.len().saturating_sub(1))
13447        }
13448        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
13449    };
13450    // Ordinary using-declarations participate in unqualified lookup at their
13451    // lexical scope. They therefore replace the resolver's terminal/global
13452    // fallback at the same or a shallower depth. A declaration in a more deeply
13453    // nested named scope is the closer lexical result and remains authoritative.
13454    // Ambiguous imports fail closed unless such a closer declaration exists.
13455    let ordinary_import_started = Instant::now();
13456    if report_stats {
13457        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
13458    }
13459    let ordinary_import_resolution = ordinary_type_import_resolution(
13460        node,
13461        components,
13462        global,
13463        analyzer,
13464        visibility,
13465        ordinary_type_imports,
13466        file,
13467        source,
13468        &lexical_scope,
13469        direct_target,
13470    );
13471    if report_stats {
13472        eprintln!(
13473            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
13474            ordinary_import_started.elapsed().as_millis(),
13475        );
13476    }
13477    let resolution = match ordinary_import_resolution {
13478        OrdinaryTypeImportResolution::Missing => normal,
13479        OrdinaryTypeImportResolution::Resolved {
13480            lexical_depth,
13481            is_direct,
13482            ..
13483        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
13484            || normal_depth.is_some_and(|depth| {
13485                depth > lexical_depth || (!is_direct && depth == lexical_depth)
13486            }) =>
13487        {
13488            normal
13489        }
13490        OrdinaryTypeImportResolution::Resolved {
13491            target,
13492            target_components,
13493            ..
13494        } => visibility.resolve_imported_type_candidate(
13495            analyzer,
13496            file,
13497            &target,
13498            &target_components,
13499            direct_target,
13500            preserve_alias,
13501        ),
13502        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
13503            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
13504        {
13505            normal
13506        }
13507        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13508    };
13509    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
13510        return resolution;
13511    }
13512
13513    // A foreign header can contribute an exact using binding from a build
13514    // configuration that is compatible with, but not implied by, the current
13515    // reference. Forward navigation has no "unproven" result channel, so it
13516    // may still navigate through one unique structured import after ordinary
13517    // lookup misses. Inverse attribution keeps using its target-guided path
13518    // and records the same evidence as unproven (#940).
13519    match compatible_foreign_type_import_resolution(
13520        node,
13521        components,
13522        global,
13523        analyzer,
13524        visibility,
13525        ordinary_type_imports,
13526        file,
13527        source,
13528        &lexical_scope,
13529        None,
13530    ) {
13531        OrdinaryTypeImportResolution::Missing => resolution,
13532        OrdinaryTypeImportResolution::Resolved {
13533            target,
13534            target_components,
13535            ..
13536        } => visibility.resolve_imported_type_candidate(
13537            analyzer,
13538            file,
13539            &target,
13540            &target_components,
13541            None,
13542            true,
13543        ),
13544        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13545    }
13546}
13547
13548fn recovered_same_file_type_alias_resolution(
13549    node: Node<'_>,
13550    components: &[String],
13551    analyzer: &CppGraphSource<'_>,
13552    visibility: &VisibilityIndex<'_>,
13553    file: &ProjectFile,
13554    direct_target: Option<&CodeUnit>,
13555    lexical_scope: &[String],
13556) -> Option<LexicalTypeResolution> {
13557    debug_assert_eq!(components.len(), 1);
13558    debug_assert!(
13559        recovered_macro_decorated_type_node(node).is_some()
13560            || macro_modifier_decorated_type_node(node)
13561    );
13562    let alias_provider = analyzer.type_alias_provider()?;
13563    for qualified in lexical_component_tiers(components, false, lexical_scope) {
13564        let candidates = visibility
13565            .visible_identifier_candidates(file, &components[0])
13566            .filter(|candidate| {
13567                candidate.source() == file
13568                    && canonical_cpp_scope_components(candidate) == qualified
13569                    && visibility
13570                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
13571            })
13572            .collect::<Vec<_>>();
13573        if candidates.is_empty() {
13574            continue;
13575        }
13576        if candidates
13577            .iter()
13578            .any(|candidate| !alias_provider.is_type_alias(candidate))
13579        {
13580            return None;
13581        }
13582        let unit = if let Some(target) = direct_target {
13583            visibility.unique_type_candidate_preserving_target(
13584                analyzer,
13585                file,
13586                &candidates,
13587                target,
13588            )?
13589        } else {
13590            let first = candidates[0];
13591            if candidates
13592                .iter()
13593                .any(|candidate| !same_visible_symbol(candidate, first))
13594            {
13595                return None;
13596            }
13597            first.clone()
13598        };
13599        return Some(LexicalTypeResolution::Resolved {
13600            unit,
13601            components: qualified,
13602            candidates: candidates.into_iter().cloned().collect(),
13603        });
13604    }
13605    None
13606}
13607
13608fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13609    matches!(
13610        structured_owner_context_resolution(node, ctx),
13611        StructuredOwnerContextResolution::SelfTarget
13612            | StructuredOwnerContextResolution::InheritedTarget
13613    )
13614}
13615
13616/// A bare/`this->` member call whose name resolves, through the enclosing class's base
13617/// hierarchy or through a lexically enclosing class, to the target member declared on
13618/// another owner. This is a genuine external usage of that owner's member rather than a
13619/// same-type self call.
13620fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13621    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
13622        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
13623    }) else {
13624        return matches!(
13625            structured_owner_context_resolution(node, ctx),
13626            StructuredOwnerContextResolution::InheritedTarget
13627        );
13628    };
13629    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13630        return false;
13631    };
13632    let chain = structured_enclosing_owner_chain(node, ctx);
13633    let Some(innermost) = chain.first() else {
13634        return false;
13635    };
13636    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13637        return false;
13638    }
13639    let Some(arity) = ctx
13640        .visibility
13641        .call_arity_evidence(ctx.file, call, ctx.source)
13642        .exact()
13643    else {
13644        return false;
13645    };
13646    for enclosing_owner in &chain {
13647        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13648            return true;
13649        }
13650        match resolve_declaring_callable_owner(
13651            &ctx.analyzer,
13652            ctx.visibility,
13653            ctx.file,
13654            cached_declaring_member_owner(enclosing_owner, ctx),
13655            &ctx.spec.member_name,
13656            arity,
13657        ) {
13658            EnclosingMemberOwnerResolution::Owner(owner) => {
13659                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13660            }
13661            EnclosingMemberOwnerResolution::Ambiguous => return false,
13662            // The name is not declared in this class or its bases, so C++
13663            // unqualified lookup continues in the next enclosing class (#3095).
13664            EnclosingMemberOwnerResolution::Missing => {}
13665        }
13666    }
13667    false
13668}
13669
13670fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13671    matches!(
13672        structured_owner_context_resolution(node, ctx),
13673        StructuredOwnerContextResolution::NonTarget
13674    )
13675}
13676
13677fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13678    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13679        return false;
13680    };
13681    let mut current = ctx.ancestry.parent(node);
13682    while let Some(parent) = current {
13683        if parent.kind() == "function_definition" {
13684            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
13685                return false;
13686            };
13687            if let Some(owners) = out_of_line_member_definition_owner(
13688                &ctx.analyzer,
13689                ctx.visibility,
13690                ctx.file,
13691                ctx.source,
13692                owner_lookup,
13693            ) && let Some((_, owner)) = owners.innermost()
13694            {
13695                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
13696            }
13697            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
13698                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13699            }
13700            return false;
13701        }
13702        current = ctx.ancestry.parent(parent);
13703    }
13704    false
13705}
13706
13707#[derive(Clone, Copy)]
13708enum StructuredOwnerContextResolution {
13709    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
13710    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
13711    SelfTarget,
13712    /// The enclosing class does not declare the member; lookup reaches the target owner
13713    /// through a base of that class, or through a lexically enclosing class (a nested
13714    /// class calling the outer class's member). Either way the reference is a genuine
13715    /// external usage OF that owner's member (e.g. `Derived` calling inherited
13716    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
13717    InheritedTarget,
13718    NonTarget,
13719    Ambiguous,
13720    Missing,
13721}
13722
13723fn structured_owner_context_resolution(
13724    node: Node<'_>,
13725    ctx: &ScanCtx<'_>,
13726) -> StructuredOwnerContextResolution {
13727    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13728        return StructuredOwnerContextResolution::Missing;
13729    };
13730    let chain = structured_enclosing_owner_chain(node, ctx);
13731    let Some(innermost) = chain.first() else {
13732        return StructuredOwnerContextResolution::Missing;
13733    };
13734    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13735        return StructuredOwnerContextResolution::SelfTarget;
13736    }
13737    // Unqualified lookup searches the enclosing class and its bases, then each lexically
13738    // enclosing class outward, and stops at the first scope that declares the name (#3095).
13739    // Only the immediately enclosing class makes a reference a same-type self call; any
13740    // other owner it reaches is a genuine usage of that owner's member.
13741    for enclosing_owner in &chain {
13742        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13743            return StructuredOwnerContextResolution::InheritedTarget;
13744        }
13745        match cached_declaring_member_owner(enclosing_owner, ctx) {
13746            EnclosingMemberOwnerResolution::Owner(owner)
13747                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
13748            {
13749                return StructuredOwnerContextResolution::InheritedTarget;
13750            }
13751            EnclosingMemberOwnerResolution::Owner(_) => {
13752                return StructuredOwnerContextResolution::NonTarget;
13753            }
13754            EnclosingMemberOwnerResolution::Ambiguous => {
13755                return StructuredOwnerContextResolution::Ambiguous;
13756            }
13757            EnclosingMemberOwnerResolution::Missing => {}
13758        }
13759    }
13760    StructuredOwnerContextResolution::Missing
13761}
13762
13763fn cached_declaring_member_owner(
13764    receiver_owner: &CodeUnit,
13765    ctx: &ScanCtx<'_>,
13766) -> EnclosingMemberOwnerResolution {
13767    if let Some(cached) = ctx
13768        .member_owner_cache
13769        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
13770            cells.borrow().get(receiver_owner).cloned()
13771        })
13772    {
13773        return cached;
13774    }
13775    let resolved = resolve_declaring_member_owner(
13776        &ctx.analyzer,
13777        ctx.visibility,
13778        ctx.file,
13779        receiver_owner,
13780        &ctx.spec.member_name,
13781    );
13782    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13783        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13784    } else {
13785        resolved
13786    };
13787    ctx.member_owner_cache
13788        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
13789            cells
13790                .borrow_mut()
13791                .insert(receiver_owner.clone(), resolved.clone())
13792        });
13793    resolved
13794}
13795
13796/// Recover a member's declaring owner when parser recovery omitted its
13797/// in-class declaration but retained an out-of-line definition. Ordinary
13798/// visible-member lookup runs first. The structured definition index then
13799/// supplies the missing member fact at each hierarchy level, so an indexed
13800/// derived override still hides the queried base member and distinct base
13801/// paths still fail closed.
13802fn indexed_declaring_owner_for_recovered_member(
13803    receiver_owner: &CodeUnit,
13804    ctx: &ScanCtx<'_>,
13805) -> EnclosingMemberOwnerResolution {
13806    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13807        return EnclosingMemberOwnerResolution::Missing;
13808    };
13809    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13810        return EnclosingMemberOwnerResolution::Missing;
13811    }
13812    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13813        return EnclosingMemberOwnerResolution::Missing;
13814    };
13815    let Some(receiver_owner) =
13816        ctx.visibility
13817            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13818    else {
13819        return EnclosingMemberOwnerResolution::Ambiguous;
13820    };
13821    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13822        ctx.visibility
13823            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13824    }) else {
13825        return EnclosingMemberOwnerResolution::Missing;
13826    };
13827
13828    let owner_declares_member = |owner: &CodeUnit| {
13829        if same_visible_symbol(owner, &target_owner) {
13830            return true;
13831        }
13832        let mut member_fq = owner.fq().clone();
13833        member_fq.push(
13834            ctx.spec
13835                .target
13836                .fq()
13837                .last()
13838                .expect("a method target has a terminal member segment"),
13839        );
13840        ctx.analyzer
13841            .definitions(&member_fq.display(segment_interner()))
13842            .any(|child| child.is_function())
13843    };
13844    if owner_declares_member(&receiver_owner) {
13845        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13846    }
13847
13848    let mut stack = hierarchy
13849        .get_direct_ancestors(&receiver_owner)
13850        .definite()
13851        .to_vec();
13852    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13853    let mut declaring_owner = None;
13854    while let Some(raw_owner) = stack.pop() {
13855        let Some(owner) =
13856            ctx.visibility
13857                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13858        else {
13859            return EnclosingMemberOwnerResolution::Ambiguous;
13860        };
13861        let propagated = propagated_counts.entry(owner.clone()).or_default();
13862        if *propagated == 2 {
13863            continue;
13864        }
13865        *propagated += 1;
13866        if owner_declares_member(&owner) {
13867            if declaring_owner.is_some() {
13868                return EnclosingMemberOwnerResolution::Ambiguous;
13869            }
13870            declaring_owner = Some(owner);
13871            continue;
13872        }
13873        stack.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
13874    }
13875    declaring_owner
13876        .map(EnclosingMemberOwnerResolution::Owner)
13877        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13878}
13879
13880/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13881/// lookup searches each in turn before it reaches the enclosing namespaces.
13882fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13883    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13884        return Vec::new();
13885    };
13886    // The innermost owner is whatever enclosing-owner resolution produced, so
13887    // it is kept as given; only the walk outward is filtered to classes.
13888    brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13889        ctx.analyzer.parent_of(unit)
13890    })
13891    .collect()
13892}
13893
13894fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13895    // Declaration recovery can index the true class/member ranges even when
13896    // the original error tree wraps that region in a bogus function. Prefer
13897    // the analyzer's exact enclosing-owner graph at the reference byte before
13898    // interpreting such a wrapper as a real callable owner.
13899    if (has_recovered_class_shape_ancestor(node)
13900        || has_malformed_wrapper_function_definition_ancestor(node))
13901        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13902    {
13903        return Some(owner);
13904    }
13905    let mut current = ctx.ancestry.parent(node);
13906    while let Some(parent) = current {
13907        if parent.kind() == "function_definition" {
13908            let owner_lookup = function_definition_owner_lookup_node(parent);
13909            if let Some(owner_lookup) = owner_lookup
13910                && let Some(owners) = out_of_line_member_definition_owner(
13911                    &ctx.analyzer,
13912                    ctx.visibility,
13913                    ctx.file,
13914                    ctx.source,
13915                    owner_lookup,
13916                )
13917                && let Some((_, owner)) = owners.innermost()
13918            {
13919                return Some(owner.clone());
13920            }
13921            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13922                return Some(owner);
13923            }
13924            if let Some(owner) = enclosing_context(parent, ctx)
13925                .owner
13926                .filter(|owner| owner.is_class())
13927            {
13928                return Some(owner);
13929            }
13930            if let Some(owner_lookup) = owner_lookup
13931                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13932            {
13933                return Some(owner);
13934            }
13935            break;
13936        }
13937        current = ctx.ancestry.parent(parent);
13938    }
13939    enclosing_context(node, ctx)
13940        .owner
13941        .filter(|owner| owner.is_class())
13942}
13943
13944fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13945    let target_owner = ctx.spec.owner.as_ref()?;
13946    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13947    let owner_name = owner_components.last()?;
13948    let mut candidates = Vec::new();
13949    for candidate in ctx
13950        .visibility
13951        .visible_identifier_candidates(ctx.file, owner_name)
13952        .filter(|candidate| candidate.is_class())
13953    {
13954        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13955            brokk_bifrost_core::analyzer::Language::Cpp,
13956            &cpp_name_for(candidate),
13957        );
13958        if !components.ends_with(&owner_components)
13959            || candidates
13960                .iter()
13961                .any(|existing| same_logical_symbol(existing, candidate))
13962        {
13963            continue;
13964        }
13965        candidates.push(candidate.clone());
13966    }
13967    let [candidate] = candidates.as_slice() else {
13968        return None;
13969    };
13970    (same_logical_symbol(candidate, target_owner)
13971        && target_group_contains_owner_peer(candidate, ctx))
13972    .then(|| candidate.clone())
13973}