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::common::same_node;
38use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
39use brokk_bifrost_core::analyzer::usages::local_inference::{
40    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
41};
42use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
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::collections::BTreeSet;
49use std::sync::Arc;
50use std::time::Instant;
51use tree_sitter::Node;
52
53#[cfg(any(test, feature = "test-support"))]
54thread_local! {
55    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
56    static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
57}
58
59#[cfg(any(test, feature = "test-support"))]
60pub fn reset_type_reference_candidate_scan_count_for_test() {
61    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
62}
63
64#[cfg(any(test, feature = "test-support"))]
65pub fn type_reference_candidate_scan_count_for_test() -> usize {
66    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
67}
68
69pub struct ScanState<'a> {
70    pub max_usages: usize,
71    pub hits: &'a mut BTreeSet<UsageHit>,
72    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
73    pub raw_match_count: &'a mut usize,
74    pub limit_exceeded: &'a mut bool,
75}
76
77pub struct ScanCtx<'a> {
78    pub analyzer: CppGraphSource<'a>,
79    pub visibility: &'a VisibilityIndex<'a>,
80    pub file: &'a ProjectFile,
81    pub source: &'a str,
82    /// The parent of every node in the file being scanned, recorded by one
83    /// downward pass when the scan starts. `Node::parent` recovers a parent by
84    /// re-descending from the root, so an ancestor climb is quadratic in depth
85    /// and the scan climbs from more than a dozen call sites per visited node
86    /// (#1927, #3097).
87    pub ancestry: ParentIndex<'a>,
88    ordinary_type_imports: OrdinaryTypeImportCell,
89    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
90    class_ranges: Option<&'a ClassRangeIndex>,
91    pub line_starts: &'a [usize],
92    pub spec: &'a TargetSpec,
93    pub target_group: &'a HashSet<CodeUnit>,
94    pub has_proven_visible_type_target: bool,
95    uses_c_semantics: bool,
96    type_reference_component_names: HashSet<String>,
97    pub target_declaration_ranges: Vec<Range>,
98    target_macro_declaration_bytes: Vec<usize>,
99    pub bindings: LocalInferenceEngine<CppScanBinding>,
100    local_shadows: LocalInferenceEngine<()>,
101    using_enum_owners: ScopedUsingEnumOwners,
102    semantic_using_enum_owners: SemanticUsingEnumOwners,
103    needs_using_enum_member_resolution: bool,
104    pub hits: &'a mut BTreeSet<UsageHit>,
105    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
106    pub raw_match_count: &'a mut usize,
107    pub max_usages: usize,
108    pub external_hit_count: usize,
109    pub limit_exceeded: &'a mut bool,
110    pub enclosing_cache: CaptureScopedMemo<RefCell<HashMap<(usize, usize), EnclosingContext>>>,
111    pub enclosing_owner_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, Option<CodeUnit>>>>,
112    lexical_scope_cache: LexicalScopeCache,
113    lexical_free_function_cache: CaptureScopedMemo<RefCell<HashMap<(String, String), bool>>>,
114    member_owner_cache:
115        CaptureScopedMemo<RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>>,
116    global_field_internal_linkage_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, bool>>>,
117    receiver_canonical_type_cache: CaptureScopedMemo<RefCell<HashMap<CodeUnit, Option<CodeUnit>>>>,
118}
119
120impl ScanCtx<'_> {
121    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
122        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
123    }
124}
125
126#[derive(Clone, Default)]
127pub struct EnclosingContext {
128    pub enclosing: Option<CodeUnit>,
129    pub owner: Option<CodeUnit>,
130}
131
132pub fn prepare_file(
133    cpp: &dyn CppSource,
134    token: QueryToken<'_>,
135    file: &ProjectFile,
136) -> Option<Arc<PreparedSyntaxTree>> {
137    cpp.prepared_syntax(token, file)
138}
139
140#[allow(clippy::too_many_arguments)]
141pub fn scan_prepared_file(
142    analyzer: &CppGraphSource<'_>,
143    visibility: &VisibilityIndex<'_>,
144    file: &ProjectFile,
145    prepared: &PreparedSyntaxTree,
146    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
147    class_ranges: Option<&ClassRangeIndex>,
148    spec: &TargetSpec,
149    target_group: &HashSet<CodeUnit>,
150    state: &mut ScanState<'_>,
151) {
152    if *state.limit_exceeded {
153        return;
154    }
155    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
156    let has_proven_visible_type_target = spec.kind == TargetKind::Type
157        && (target_group.iter().any(|target| {
158            same_logical_symbol(target, &spec.target)
159                && visibility.is_physically_visible(file, target)
160        }) || {
161            let candidates = visibility
162                .visible_identifier_candidates(file, spec.target.identifier())
163                .collect::<Vec<_>>();
164            visibility.c_tag_declaration_family_matches_target(
165                analyzer,
166                file,
167                &candidates,
168                &spec.target,
169            )
170        });
171    if spec.kind == TargetKind::Type
172        && !has_proven_visible_type_target
173        && visibility
174            .visible_identifier_candidates(file, spec.target.identifier())
175            .any(|candidate| {
176                candidate != &spec.target
177                    && !target_group.contains(candidate)
178                    && same_logical_symbol(candidate, &spec.target)
179                    && visibility.is_physically_visible(file, candidate)
180                    && !visibility.c_tag_declaration_family_matches_target(
181                        analyzer,
182                        file,
183                        std::slice::from_ref(&candidate),
184                        &spec.target,
185                    )
186            })
187    {
188        return;
189    }
190    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
191        analyzer.ranges(&spec.target)
192    } else {
193        Vec::new()
194    };
195    let target_declaration_ranges = if spec.kind == TargetKind::Type {
196        target_group
197            .iter()
198            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
199            .flat_map(|target| analyzer.ranges(target))
200            .collect()
201    } else if spec.target.source() == file {
202        if spec.kind == TargetKind::Macro {
203            macro_target_declaration_ranges.clone()
204        } else {
205            analyzer.ranges(&spec.target)
206        }
207    } else {
208        Vec::new()
209    };
210    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
211        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
212    } else {
213        Vec::new()
214    };
215    let type_reference_component_names = if spec.kind == TargetKind::Type {
216        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
217    } else {
218        HashSet::default()
219    };
220    if spec.kind == TargetKind::Type
221        && !file_may_reference_type_target(
222            prepared.tree().root_node(),
223            prepared.source(),
224            analyzer,
225            visibility,
226            file,
227            &spec.target,
228            &type_reference_component_names,
229        )
230    {
231        return;
232    }
233    let ordinary_type_imports = initialized_ordinary_type_imports(
234        prepared.tree().root_node(),
235        analyzer,
236        visibility,
237        file,
238        prepared.source(),
239    );
240    let external_hit_count = state
241        .hits
242        .iter()
243        .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
244        .count();
245    let mut ctx = ScanCtx {
246        analyzer: *analyzer,
247        visibility,
248        file,
249        source: prepared.source(),
250        ancestry: ParentIndex::new(prepared.tree().root_node()),
251        ordinary_type_imports,
252        recovered_sentinel_classes,
253        class_ranges,
254        line_starts: prepared.line_starts(),
255        spec,
256        target_group,
257        has_proven_visible_type_target,
258        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
259        type_reference_component_names,
260        target_declaration_ranges,
261        target_macro_declaration_bytes,
262        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
263        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
264        using_enum_owners: ScopedUsingEnumOwners::new(),
265        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
266        needs_using_enum_member_resolution,
267        hits: state.hits,
268        unproven_hits: state.unproven_hits,
269        raw_match_count: state.raw_match_count,
270        max_usages: state.max_usages,
271        external_hit_count,
272        limit_exceeded: state.limit_exceeded,
273        enclosing_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
274        enclosing_owner_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
275        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
276        lexical_free_function_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
277        member_owner_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
278        global_field_internal_linkage_cache: CaptureScopedMemo::new(RefCell::new(
279            HashMap::default(),
280        )),
281        receiver_canonical_type_cache: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
282    };
283    if needs_using_enum_member_resolution {
284        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
285    }
286    if spec.kind == TargetKind::Macro {
287        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
288        return;
289    }
290    scan_node(prepared.tree().root_node(), &mut ctx);
291}
292
293/// Scan a macro target without maintaining the declaration and lexical state
294/// required by ordinary C and C++ symbols. Macro activation comes entirely
295/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
296/// node makes generated tables needlessly expensive.
297fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
298    walk_named_tree_preorder(root, true, |node| {
299        if *ctx.limit_exceeded {
300            return WalkControl::Break;
301        }
302        maybe_record_macro_hit(node, ctx);
303        WalkControl::Continue
304    });
305}
306
307enum UsingEnumDeclarationScope {
308    Block,
309    Class(CodeUnit),
310    Namespace(Vec<String>),
311    UnsupportedClass,
312}
313
314fn resolved_using_enum_declaration(
315    node: Node<'_>,
316    ctx: &ScanCtx<'_>,
317) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
318    let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
319        node,
320        &ctx.analyzer,
321        ctx.visibility,
322        &ctx.ordinary_type_imports,
323        ctx.file,
324        ctx.source,
325    ) else {
326        return None;
327    };
328    Some((unit, using_enum_declaration_scope(node, ctx)))
329}
330
331fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
332    let mut current = ctx.ancestry.parent(node);
333    while let Some(parent) = current {
334        if matches!(
335            parent.kind(),
336            "compound_statement"
337                | "function_definition"
338                | "lambda_expression"
339                | "for_statement"
340                | "while_statement"
341                | "if_statement"
342        ) {
343            return UsingEnumDeclarationScope::Block;
344        }
345        if matches!(
346            parent.kind(),
347            "class_specifier" | "struct_specifier" | "union_specifier"
348        ) {
349            let resolution = enclosing_lexical_scope_components(
350                node,
351                &ctx.analyzer,
352                ctx.visibility,
353                ctx.file,
354                ctx.source,
355            );
356            if let LexicalScopeResolution::Resolved(components) = resolution
357                && let LexicalTypeResolution::Resolved { unit, .. } =
358                    ctx.visibility.resolve_type_components_lexically(
359                        &ctx.analyzer,
360                        ctx.file,
361                        &components,
362                        true,
363                        &[],
364                    )
365            {
366                return UsingEnumDeclarationScope::Class(unit);
367            }
368            return UsingEnumDeclarationScope::UnsupportedClass;
369        }
370        current = ctx.ancestry.parent(parent);
371    }
372    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
373}
374
375fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
376    let mut stack = vec![root];
377    while let Some(node) = stack.pop() {
378        if node.kind() == "using_declaration"
379            && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
380        {
381            match scope {
382                UsingEnumDeclarationScope::Block => {}
383                UsingEnumDeclarationScope::Class(class) => {
384                    ctx.semantic_using_enum_owners.import_class(class, unit);
385                }
386                UsingEnumDeclarationScope::Namespace(namespace) => {
387                    ctx.semantic_using_enum_owners.import_namespace(
388                        namespace,
389                        node.start_byte(),
390                        unit,
391                    );
392                }
393                UsingEnumDeclarationScope::UnsupportedClass => {}
394            }
395        }
396        push_named_children_reversed(node, &mut stack);
397    }
398}
399
400fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
401    struct Frame<'tree> {
402        node: Node<'tree>,
403        next_child: Option<Node<'tree>>,
404        previous_child: Option<Node<'tree>>,
405        entered: bool,
406        enters_scope: bool,
407        enters_using_enum_scope: bool,
408        fractured_function_scope: bool,
409    }
410    impl<'tree> Frame<'tree> {
411        fn new(node: Node<'tree>) -> Self {
412            Self {
413                node,
414                next_child: node.named_child(0),
415                previous_child: None,
416                entered: false,
417                enters_scope: false,
418                enters_using_enum_scope: false,
419                fractured_function_scope: false,
420            }
421        }
422    }
423    if *ctx.limit_exceeded {
424        return;
425    }
426    let mut stack = vec![Frame::new(node)];
427    while let Some(current) = stack.last_mut() {
428        let node = current.node;
429        let translation_unit = node.kind() == "translation_unit";
430        if !current.entered {
431            current.entered = true;
432            current.enters_scope = matches!(
433                node.kind(),
434                "compound_statement"
435                    | "function_definition"
436                    | "lambda_expression"
437                    | "for_statement"
438                    | "for_range_loop"
439                    | "while_statement"
440                    | "if_statement"
441            );
442            current.enters_using_enum_scope = ctx.needs_using_enum_member_resolution
443                && (current.enters_scope
444                    || matches!(
445                        node.kind(),
446                        "namespace_definition"
447                            | "class_specifier"
448                            | "struct_specifier"
449                            | "union_specifier"
450                    ));
451            if current.enters_scope {
452                ctx.bindings.enter_scope();
453                ctx.local_shadows.enter_scope();
454            }
455            if current.enters_using_enum_scope {
456                ctx.using_enum_owners.enter_scope();
457            }
458            seed_declarations(node, ctx);
459            maybe_record_hit(node, ctx);
460        }
461        if let Some(child) = current.previous_child.take()
462            && !*ctx.limit_exceeded
463            && translation_unit
464            && macro_fractured_function(child)
465            && macro_fracture_boundary(child).is_some()
466        {
467            if current.fractured_function_scope {
468                ctx.bindings.exit_scope();
469                ctx.local_shadows.exit_scope();
470            }
471            ctx.bindings.enter_scope();
472            ctx.local_shadows.enter_scope();
473            seed_fractured_function_prefix(child, ctx);
474            current.fractured_function_scope = true;
475        }
476        // Unwind every entered scope once the hit limit has been reached.
477        if !*ctx.limit_exceeded
478            && let Some(child) = current.next_child.take()
479        {
480            current.next_child = child.next_named_sibling();
481            if translation_unit && current.fractured_function_scope && child.kind() == "ERROR" {
482                ctx.bindings.exit_scope();
483                ctx.local_shadows.exit_scope();
484                current.fractured_function_scope = false;
485            }
486            current.previous_child = Some(child);
487            stack.push(Frame::new(child));
488            continue;
489        }
490        if current.fractured_function_scope {
491            ctx.bindings.exit_scope();
492            ctx.local_shadows.exit_scope();
493        }
494        if current.enters_scope {
495            ctx.bindings.exit_scope();
496            ctx.local_shadows.exit_scope();
497        }
498        if current.enters_using_enum_scope {
499            ctx.using_enum_owners.exit_scope();
500        }
501        stack.pop();
502    }
503}
504
505fn macro_fractured_function(node: Node<'_>) -> bool {
506    if node.kind() != "function_definition" {
507        return false;
508    }
509    let Some(body) = node.child_by_field_name("body") else {
510        return false;
511    };
512    let mut stack = vec![body];
513    while let Some(current) = stack.pop() {
514        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
515            return true;
516        }
517        let mut cursor = current.walk();
518        stack.extend(current.named_children(&mut cursor));
519    }
520    false
521}
522
523/// Return the recovery boundary following a fractured macro-generated callable.
524pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
525    let first_orphan = node.next_named_sibling()?;
526    if !matches!(
527        first_orphan.kind(),
528        "expression_statement"
529            | "compound_statement"
530            | "preproc_if"
531            | "preproc_ifdef"
532            | "if_statement"
533            | "for_statement"
534            | "while_statement"
535            | "do_statement"
536            | "return_statement"
537    ) {
538        return None;
539    }
540    let mut sibling = Some(first_orphan);
541    while let Some(current) = sibling {
542        if current.kind() == "ERROR" {
543            return Some(current.end_byte());
544        }
545        if matches!(
546            current.kind(),
547            "function_definition" | "declaration" | "type_definition"
548        ) {
549            return None;
550        }
551        sibling = current.next_named_sibling();
552    }
553    None
554}
555
556fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
557    let cutoff = node.end_byte().saturating_sub(1);
558    seed_fractured_active_path(node, cutoff, ctx);
559}
560
561fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
562    let mut stack = vec![node];
563    while let Some(node) = stack.pop() {
564        if node.start_byte() >= cutoff {
565            continue;
566        }
567        let enters_scope = matches!(
568            node.kind(),
569            "compound_statement"
570                | "function_definition"
571                | "lambda_expression"
572                | "for_range_loop"
573                | "for_statement"
574                | "while_statement"
575                | "if_statement"
576                | "class_specifier"
577                | "struct_specifier"
578                | "union_specifier"
579        );
580        if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
581            continue;
582        }
583        seed_declarations(node, ctx);
584        push_named_children_reversed(node, &mut stack);
585    }
586}
587
588fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
589    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
590        || (ctx.spec.kind != TargetKind::Type
591            && indexed_recovered_class_field_declaration(node, ctx))
592    {
593        return;
594    }
595    match node.kind() {
596        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
597        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
598        "for_range_loop" => seed_range_binding(node, ctx),
599        "expression_statement" | "macro_type_specifier" => {
600            seed_function_macro_local_binding(node, ctx)
601        }
602        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
603        "using_declaration" => seed_using_enum(node, ctx),
604        _ => {}
605    }
606}
607
608fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
609    let Some(binding) = ctx
610        .visibility
611        .function_macro_local_binding(ctx.file, node, ctx.source)
612    else {
613        return;
614    };
615    if ctx.spec.kind == TargetKind::Type {
616        ctx.bindings.declare_shadow(binding.name);
617        return;
618    }
619    let normalized = normalize_cpp_type_name(&binding.type_name);
620    let unit = binding
621        .type_node
622        .and_then(|type_node| {
623            ctx.visibility
624                .resolve_type_node_result(ctx.file, type_node, ctx.source)
625                .ok()
626                .flatten()
627        })
628        .or_else(|| {
629            ctx.visibility
630                .canonical_type_for_reference(ctx.file, &normalized)
631        })
632        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
633    ctx.bindings.seed_symbol(
634        binding.name,
635        CppScanBinding::from_type_name(
636            normalized,
637            unit,
638            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
639        ),
640    );
641}
642
643fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
644    let Some(binding) =
645        ctx.visibility
646            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
647    else {
648        return;
649    };
650    let normalized = normalize_cpp_type_name(&binding.type_name);
651    let unit = binding.proven_unit.clone().or_else(|| {
652        binding
653            .type_node
654            .and_then(|type_node| {
655                ctx.visibility
656                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
657                    .ok()
658                    .flatten()
659            })
660            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
661    });
662    if let Some(unit) = unit {
663        ctx.bindings.seed_symbol(
664            binding.name,
665            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
666        );
667    }
668}
669
670fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
671    if node.kind() != "declaration"
672        || !has_function_scope_ancestor(node)
673        || has_ancestor_kind(node, "lambda_expression")
674        || !(has_recovered_class_shape_ancestor(node)
675            || has_malformed_wrapper_function_definition_ancestor(node))
676    {
677        return false;
678    }
679    let context = enclosing_context(node, ctx);
680    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
681        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
682}
683
684fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
685    if !ctx.needs_using_enum_member_resolution {
686        return;
687    }
688    if let Some((unit, UsingEnumDeclarationScope::Block)) =
689        resolved_using_enum_declaration(node, ctx)
690    {
691        ctx.using_enum_owners.import(unit);
692    }
693}
694
695fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
696    // Class and local-struct fields are resolved from their enclosing owner
697    // when they appear as an unqualified receiver. Seeding them into the
698    // function-wide binding scope would make same-spelled fields from sibling
699    // owners overwrite one another before the owner-aware path runs.
700    if node.kind() == "field_declaration" {
701        return;
702    }
703    let type_node = node
704        .child_by_field_name("type")
705        .or_else(|| first_type_child(node));
706    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
707    let mut cursor = node.walk();
708    for child in node.named_children(&mut cursor) {
709        let declarator = declaration_declarator(node, child);
710        let Some(declarator) = declarator else {
711            continue;
712        };
713        let Some(name) = extract_variable_name(declarator, ctx.source) else {
714            continue;
715        };
716        if declarator.kind() == "function_declarator"
717            && !constructor_style_local_declaration(
718                ctx.visibility,
719                ctx.file,
720                ctx.source,
721                declarator,
722                type_text.as_deref(),
723                &ctx.bindings,
724            )
725        {
726            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
727                ctx.local_shadows.declare_shadow(name);
728            }
729            continue;
730        }
731        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
732            ctx.local_shadows.declare_shadow(name.clone());
733        }
734        if ctx.spec.kind == TargetKind::Type {
735            ctx.bindings.declare_shadow(name);
736            continue;
737        }
738        let value = child.child_by_field_name("value");
739        seed_binding_from_type_or_value(&name, type_node, value, ctx);
740    }
741}
742
743fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
744    if !parameter_belongs_to_callable_scope(node) {
745        return;
746    }
747    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
748        return;
749    };
750    if has_function_scope_ancestor(node) {
751        ctx.local_shadows.declare_shadow(name.clone());
752    }
753    if ctx.spec.kind == TargetKind::Type {
754        ctx.bindings.declare_shadow(name);
755        return;
756    }
757    seed_binding_from_type_or_value(&name, type_node, None, ctx);
758}
759
760fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
761    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
762        return;
763    };
764    if has_function_scope_ancestor(node) {
765        ctx.local_shadows.declare_shadow(name.clone());
766    }
767    if ctx.spec.kind == TargetKind::Type {
768        ctx.bindings.declare_shadow(name);
769        return;
770    }
771    seed_binding_from_type_or_value(&name, type_node, None, ctx);
772}
773
774fn has_function_scope_ancestor(node: Node<'_>) -> bool {
775    let mut current = node.parent();
776    while let Some(parent) = current {
777        // Error recovery can wrap a complete namespace in a bogus outer
778        // `function_definition` (for example when an object-like namespace
779        // macro is parsed as a return type).  Declarations below that
780        // namespace are still namespace-scoped: do not let the malformed
781        // callable envelope seed them as local shadows.  A real function
782        // body is encountered before its enclosing namespace, so this keeps
783        // ordinary local binding detection unchanged.
784        if parent.kind() == "namespace_definition" {
785            return false;
786        }
787        if parent.kind() == "function_definition" {
788            // The malformed sentinel envelope is not a callable scope. Its
789            // body may contain a recovered namespace (or, in a smaller error
790            // tree, only an ERROR node standing in for that namespace), so
791            // declarations directly below it must remain namespace-scoped.
792            // Real nested functions are encountered first and still seed
793            // ordinary local bindings.
794            return !is_malformed_wrapper_function_definition(parent);
795        }
796        if parent.kind() == "lambda_expression" {
797            return true;
798        }
799        current = parent.parent();
800    }
801    false
802}
803
804fn seed_binding_from_type_or_value(
805    name: &str,
806    type_node: Option<Node<'_>>,
807    value: Option<Node<'_>>,
808    ctx: &mut ScanCtx<'_>,
809) {
810    if name.is_empty() {
811        return;
812    }
813    let resolved = type_node
814        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
815        .map(|node| {
816            let text = node_text(node, ctx.source);
817            let name = normalize_cpp_type_name(text);
818            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
819                return CppScanBinding::from_type_name(
820                    name,
821                    Some(unit),
822                    cpp_type_text_pointer_depth(text),
823                );
824            }
825            if let Some(unit) = function_local_type_for_node(node, ctx) {
826                return CppScanBinding::from_type_name(
827                    name,
828                    unit,
829                    cpp_type_text_pointer_depth(text),
830                );
831            }
832            // Bare type names need lexical ownership before the coarse visible-name
833            // fallback (two namespaces can each declare `CopyResult`). Template
834            // references keep the specialization-aware resolver first because a
835            // component-only lexical lookup cannot rank partial specializations.
836            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
837                if cpp_template_reference_arguments(node, ctx.source).is_some() {
838                    return None;
839                }
840                match enclosing_lexical_scope_components(
841                    node,
842                    &ctx.analyzer,
843                    ctx.visibility,
844                    ctx.file,
845                    ctx.source,
846                ) {
847                    LexicalScopeResolution::Resolved(scope) => Some(scope),
848                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
849                }
850            });
851            let unit = lexical_scope
852                .as_deref()
853                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
854                .or_else(|| {
855                    match ctx
856                        .visibility
857                        .resolve_type_node_result(ctx.file, node, ctx.source)
858                    {
859                        Ok(Some(unit)) => Some(unit),
860                        Ok(None) => ctx
861                            .visibility
862                            .canonical_type_for_reference(ctx.file, &name)
863                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
864                        Err(_) => None,
865                    }
866                });
867            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
868        })
869        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
870
871    if let Some(resolved) = resolved {
872        ctx.bindings.seed_symbol(name.to_string(), resolved);
873    } else if let Some(value) = value
874        && value.kind() == "identifier"
875    {
876        ctx.bindings
877            .alias_symbol(name.to_string(), node_text(value, ctx.source));
878    } else {
879        ctx.bindings.declare_shadow(name.to_string());
880    }
881}
882
883fn resolve_seed_type_node_lexically(
884    node: Node<'_>,
885    ctx: &ScanCtx<'_>,
886    scope: &[String],
887) -> Option<CodeUnit> {
888    let (components, global) = type_reference_components(node, ctx.source)?;
889    let resolution = match scan_owner_type(ctx) {
890        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
891            &ctx.analyzer,
892            ctx.file,
893            &components,
894            global,
895            scope,
896            target,
897        ),
898        None => ctx.visibility.resolve_type_components_lexically(
899            &ctx.analyzer,
900            ctx.file,
901            &components,
902            global,
903            scope,
904        ),
905    };
906    match resolution {
907        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
908        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
909    }
910}
911
912/// The type this scan is asking about: the owner of a member target, or the
913/// target itself when it is a type.
914///
915/// C headers routinely declare one typedef name once per branch of an
916/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
917/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
918/// are alternate spellings of one name rather than competing declarations, and
919/// canonicalizing without the scan's own type identity picks between them by
920/// declaration order. That ordering can bind a receiver to the branch that does
921/// not declare the queried member, which drops every reference in the branch
922/// that does. Naming the identity keeps the target-preserving rule the forward
923/// resolver already applies (`unique_type_candidate_preserving_target`).
924fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
925    // Target-preserving lookup can reconcile logical declarations across files.
926    // A receiver must not borrow an owner from a header its caller cannot see.
927    ctx.spec
928        .owner
929        .as_ref()
930        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
931}
932
933const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
934
935fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
936    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
937}
938
939fn infer_type_from_value_with_budget(
940    node: Node<'_>,
941    ctx: &ScanCtx<'_>,
942    remaining_call_depth: usize,
943) -> Option<CppScanBinding> {
944    match node.kind() {
945        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
946            infer_cpp_initializer_binding(
947                &ctx.analyzer,
948                ctx.visibility,
949                ctx.file,
950                ctx.source,
951                node,
952                None,
953            )
954        }
955        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
956            &ctx.analyzer,
957            ctx.visibility,
958            ctx.file,
959            ctx.source,
960            node,
961            Some(&|receiver, source| {
962                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
963            }),
964        ),
965        "initializer_list" => None,
966        "identifier" => {
967            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
968            resolved
969                .as_precise()?
970                .iter()
971                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
972                .cloned()
973        }
974        _ => {
975            let text = node_text(node, ctx.source);
976            let name = normalize_cpp_type_name(text);
977            ctx.visibility
978                .resolve_type(ctx.file, &name)
979                .map(|unit| CppScanBinding::from_unit(unit, 0))
980        }
981    }
982}
983
984/// Whether a member named `identifier` can be spelled anywhere in this file's
985/// syntax.
986///
987/// Both member scans below -- [`maybe_record_method_hit`] and
988/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
989/// of a syntax node against the target's member name, so a file whose syntax
990/// never spells that name yields neither a proven nor an unproven hit. That
991/// makes this an exact admission test rather than an approximation: candidate
992/// discovery can drop such a file without narrowing what the query proves.
993///
994/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
995/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
996/// replacement, which
997/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
998/// sentinel parse, and an `ERROR` region -- so those admit on containment
999/// instead of on a token match. A `comment` is the one subtree no scan reads,
1000/// so it is skipped.
1001///
1002/// Defined only for a plain identifier: [`name_matches_callable`] widens the
1003/// comparison for an `operator` name to the `operator` token alone, and a
1004/// destructor or conversion-function name is not spelled the way it is
1005/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
1006/// that condition.
1007pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
1008    debug_assert!(
1009        cpp_member_is_spelled_at_references(identifier),
1010        "member admission is defined only for a plain identifier: {identifier:?}"
1011    );
1012    let mut spelled = false;
1013    walk_named_tree_preorder(root, true, |node| match node.kind() {
1014        "comment" => WalkControl::SkipChildren,
1015        "identifier"
1016        | "field_identifier"
1017        | "type_identifier"
1018        | "namespace_identifier"
1019        | "statement_identifier" => {
1020            if node_text(node, source).trim() == identifier {
1021                spelled = true;
1022                WalkControl::Break
1023            } else {
1024                WalkControl::SkipChildren
1025            }
1026        }
1027        "preproc_arg" | "ERROR" => {
1028            if node_text(node, source).contains(identifier) {
1029                spelled = true;
1030                WalkControl::Break
1031            } else {
1032                // The node's own text covers every descendant's bytes, so a
1033                // miss here is a miss for the whole subtree.
1034                WalkControl::SkipChildren
1035            }
1036        }
1037        _ => WalkControl::Continue,
1038    });
1039    spelled
1040}
1041
1042/// Whether every reference to a member named `identifier` must spell it.
1043///
1044/// An `operator` name is matched by [`name_matches_callable`] through the bare
1045/// `operator` token, and a destructor or conversion function is recorded under
1046/// a name its call sites do not write, so neither admits a spelling test.
1047pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1048    let mut characters = identifier.chars();
1049    characters
1050        .next()
1051        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1052        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1053        && !identifier.starts_with("operator")
1054}
1055
1056fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1057    match ctx.spec.kind {
1058        TargetKind::Type => maybe_record_type_hit(node, ctx),
1059        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1060        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1061        TargetKind::Method => maybe_record_method_hit(node, ctx),
1062        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1063        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1064        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1065    }
1066}
1067
1068fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1069    if node_text(node, ctx.source) != ctx.spec.member_name {
1070        return;
1071    }
1072    if is_ordinary_macro_reference_node(node) {
1073        if ctx.visibility.macro_binding_matches_target_declaration_at(
1074            ctx.file,
1075            &ctx.spec.member_name,
1076            node.start_byte(),
1077            ctx.spec.target.source(),
1078            &ctx.target_macro_declaration_bytes,
1079        ) {
1080            *ctx.raw_match_count += 1;
1081            push_hit(node, ctx);
1082            return;
1083        }
1084        match ctx.visibility.resolve_ordinary_macro_reference(
1085            &ctx.analyzer,
1086            ctx.file,
1087            node,
1088            ctx.source,
1089        ) {
1090            OrdinaryMacroReferenceResolution::Resolved(unit)
1091                if ctx.target_group.contains(&unit) =>
1092            {
1093                *ctx.raw_match_count += 1;
1094                push_hit(node, ctx);
1095            }
1096            OrdinaryMacroReferenceResolution::Ambiguous
1097                if ctx
1098                    .visibility
1099                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1100            {
1101                *ctx.raw_match_count += 1;
1102                push_unproven_hit(node, ctx);
1103            }
1104            OrdinaryMacroReferenceResolution::Resolved(_)
1105            | OrdinaryMacroReferenceResolution::Ambiguous
1106            | OrdinaryMacroReferenceResolution::Missing => {}
1107        }
1108        return;
1109    }
1110    if !matches!(
1111        node.kind(),
1112        "identifier"
1113            | "field_identifier"
1114            | "type_identifier"
1115            | "namespace_identifier"
1116            | "preproc_arg"
1117    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1118        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1119            && parent
1120                .child_by_field_name("name")
1121                .is_some_and(|name| same_node(name, node))
1122    }) {
1123        return;
1124    }
1125    if ctx.visibility.macro_binding_matches_target_declaration_at(
1126        ctx.file,
1127        &ctx.spec.member_name,
1128        node.start_byte(),
1129        ctx.spec.target.source(),
1130        &ctx.target_macro_declaration_bytes,
1131    ) {
1132        *ctx.raw_match_count += 1;
1133        push_hit(node, ctx);
1134    } else if ctx.visibility.macro_name_may_be_bound_at(
1135        ctx.file,
1136        &ctx.spec.member_name,
1137        node.start_byte(),
1138    ) && ctx
1139        .visibility
1140        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1141    {
1142        *ctx.raw_match_count += 1;
1143        push_unproven_hit(node, ctx);
1144    }
1145}
1146
1147fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1148    let kind = node.kind();
1149    if ctx.uses_c_semantics
1150        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1151            .is_some()
1152    {
1153        return;
1154    }
1155    if kind == "preproc_arg" {
1156        maybe_record_object_macro_replacement_type_hits(node, ctx);
1157        return;
1158    }
1159    if let Some(type_node) = ctx
1160        .visibility
1161        .function_macro_type_argument(ctx.file, node, ctx.source)
1162    {
1163        if ctx
1164            .local_shadows
1165            .is_shadowed(node_text(type_node, ctx.source))
1166        {
1167            return;
1168        }
1169        if let LexicalTypeResolution::Resolved {
1170            unit, candidates, ..
1171        } = resolve_type_node_lexically_for_target(
1172            type_node,
1173            &ctx.analyzer,
1174            ctx.visibility,
1175            &ctx.ordinary_type_imports,
1176            ctx.file,
1177            ctx.source,
1178            &ctx.spec.target,
1179            Some(&ctx.lexical_scope_cache),
1180            ctx.recovered_sentinel_scope(type_node).as_deref(),
1181        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1182        {
1183            *ctx.raw_match_count += 1;
1184            push_type_hit(type_node, ctx);
1185        }
1186        return;
1187    }
1188    let recovered_exported_class_base =
1189        matches!(
1190            kind,
1191            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1192        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1193    if kind == "field_declaration"
1194        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1195    {
1196        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1197        return;
1198    }
1199    if kind == "qualified_identifier"
1200        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1201    {
1202        // A member-pointer owner can itself end in a nested alias, as in
1203        // `type_identity<T>::type::*`. Resolving the complete owner
1204        // canonicalizes that alias to its underlying type and loses the alias
1205        // declaration that inverse lookup is targeting. Retain every
1206        // structurally proven qualifier component before asking for the
1207        // canonical owner type.
1208        if ctx
1209            .analyzer
1210            .type_alias_provider()
1211            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1212            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1213            && let Some(terminal) = owner.nodes.last().copied()
1214        {
1215            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1216                return;
1217            }
1218            if ctx.visibility.external_type_candidate_visible_in_context(
1219                &ctx.analyzer,
1220                ctx.file,
1221                &ctx.spec.target,
1222                terminal,
1223            ) || ctx
1224                .visibility
1225                .dependent_member_pointer_alias_visible_in_context(
1226                    &ctx.analyzer,
1227                    ctx.file,
1228                    &ctx.spec.target,
1229                    &owner.names,
1230                    terminal,
1231                )
1232            {
1233                *ctx.raw_match_count += 1;
1234                push_type_hit(terminal, ctx);
1235            } else if ctx
1236                .visibility
1237                .is_physically_visible(ctx.file, &ctx.spec.target)
1238                && ctx.visibility.declaration_guards_compatible_at_reference(
1239                    &ctx.analyzer,
1240                    ctx.file,
1241                    &ctx.spec.target,
1242                    terminal,
1243                )
1244            {
1245                // The structured owner and terminal identify a candidate, but
1246                // do not establish its conditional availability.
1247                *ctx.raw_match_count += 1;
1248                push_unproven_hit(terminal, ctx);
1249            }
1250            return;
1251        }
1252        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1253            *ctx.raw_match_count += 1;
1254            for scope in scopes {
1255                push_type_hit(scope, ctx);
1256            }
1257            return;
1258        }
1259        return;
1260    }
1261    if kind == "pointer_expression"
1262        && let Some(value) = qualified_callable_value(node)
1263        && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1264    {
1265        *ctx.raw_match_count += 1;
1266        match owner_hit {
1267            TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1268            TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1269        }
1270        return;
1271    }
1272    if kind == "call_expression" {
1273        maybe_record_direct_temporary_type_hit(node, ctx);
1274        return;
1275    }
1276    if !matches!(
1277        kind,
1278        "identifier"
1279            | "namespace_identifier"
1280            | "qualified_identifier"
1281            | "scoped_identifier"
1282            | "scoped_type_identifier"
1283            | "template_function"
1284            | "template_type"
1285            | "type_descriptor"
1286            | "type_identifier"
1287            | "using_declaration"
1288    ) {
1289        return;
1290    }
1291    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1292    let template_function_type = cpp_template_function_has_type_owner(node);
1293    let recovered_qualified_friend =
1294        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1295    if !recovered_type
1296        && !recovered_qualified_friend
1297        && !recovered_exported_class_base
1298        && matches!(
1299            kind,
1300            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1301        )
1302        && !type_reference_components_may_name_target(node, ctx)
1303    {
1304        return;
1305    }
1306    if ctx.ancestry.parent(node).is_some_and(|parent| {
1307        parent.kind() == "operator_cast"
1308            && parent
1309                .child_by_field_name("type")
1310                .is_some_and(|target| same_node(target, node))
1311    }) {
1312        return;
1313    }
1314    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1315        *ctx.raw_match_count += 1;
1316        push_type_hit(hit, ctx);
1317        return;
1318    }
1319    if matches!(kind, "identifier" | "template_function")
1320        && parent_if_field(node, "call_expression", "function").is_some()
1321    {
1322        return;
1323    }
1324    if kind == "using_declaration" {
1325        let (resolution, type_node) =
1326            if let Some(type_node) = using_enum_declaration_type_node(node) {
1327                (
1328                    resolve_using_enum_declaration_owner(
1329                        node,
1330                        &ctx.analyzer,
1331                        ctx.visibility,
1332                        &ctx.ordinary_type_imports,
1333                        ctx.file,
1334                        ctx.source,
1335                    ),
1336                    type_node,
1337                )
1338            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1339                (
1340                    resolve_ordinary_using_declaration_owner(
1341                        node,
1342                        &ctx.analyzer,
1343                        ctx.visibility,
1344                        ctx.file,
1345                        ctx.source,
1346                    ),
1347                    type_node,
1348                )
1349            } else {
1350                return;
1351            };
1352        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1353            && same_visible_symbol(&unit, &ctx.spec.target)
1354        {
1355            *ctx.raw_match_count += 1;
1356            push_type_hit(type_node, ctx);
1357        }
1358        return;
1359    }
1360    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1361        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1362            || local_type_name_shadows(node, ctx)
1363        {
1364            return;
1365        }
1366        if let LexicalTypeResolution::Resolved {
1367            unit, candidates, ..
1368        } = resolve_type_node_lexically_for_target(
1369            node,
1370            &ctx.analyzer,
1371            ctx.visibility,
1372            &ctx.ordinary_type_imports,
1373            ctx.file,
1374            ctx.source,
1375            &ctx.spec.target,
1376            Some(&ctx.lexical_scope_cache),
1377            ctx.recovered_sentinel_scope(node).as_deref(),
1378        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1379        {
1380            *ctx.raw_match_count += 1;
1381            push_type_hit(node, ctx);
1382        }
1383        return;
1384    }
1385    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1386        // A missing `::` can put either the macro or the real type in the
1387        // recovered scope. Resolve both candidates against this inverse
1388        // target before choosing one: a unique match is routed through the
1389        // ordinary target-guided path, while two distinct matches are
1390        // ambiguous and must not invent a hit. With no target match, retain
1391        // the existing recovered-scope path below for its conservative
1392        // fallback behaviour.
1393        let mut matching = Vec::new();
1394        for candidate in [node, type_node] {
1395            if matching
1396                .iter()
1397                .any(|existing| same_node(*existing, candidate))
1398            {
1399                continue;
1400            }
1401            if let LexicalTypeResolution::Resolved {
1402                unit, candidates, ..
1403            } = resolve_type_node_lexically_for_target(
1404                candidate,
1405                &ctx.analyzer,
1406                ctx.visibility,
1407                &ctx.ordinary_type_imports,
1408                ctx.file,
1409                ctx.source,
1410                &ctx.spec.target,
1411                Some(&ctx.lexical_scope_cache),
1412                ctx.recovered_sentinel_scope(candidate).as_deref(),
1413            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1414            {
1415                matching.push(candidate);
1416            }
1417        }
1418        match matching.as_slice() {
1419            [candidate] if !same_node(*candidate, node) => {
1420                maybe_record_type_hit(*candidate, ctx);
1421                return;
1422            }
1423            [candidate] if same_node(*candidate, node) => {}
1424            [] => {}
1425            _ => return,
1426        }
1427    }
1428    if !recovered_type
1429        && !template_function_type
1430        && !matches!(
1431            kind,
1432            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1433        )
1434    {
1435        return;
1436    }
1437    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1438        components.len() == 1
1439            && !global
1440            && (local_type_name_shadows(node, ctx)
1441                || ctx
1442                    .analyzer
1443                    .type_alias_provider()
1444                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1445                    && nearer_type_name_shadows_structured_reference(node, ctx))
1446    }) {
1447        return;
1448    }
1449    if !local_class_target_reachable(node, ctx) {
1450        return;
1451    }
1452    if !recovered_type
1453        && !recovered_qualified_friend
1454        && !recovered_exported_class_base
1455        && ctx.ancestry.parent(node).is_some_and(|parent| {
1456            parent.kind() == "alias_declaration"
1457                && parent
1458                    .child_by_field_name("name")
1459                    .is_some_and(|name| same_node(name, node))
1460        })
1461    {
1462        return;
1463    }
1464    #[cfg(any(test, feature = "test-support"))]
1465    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1466    if !recovered_type
1467        && !recovered_qualified_friend
1468        && !recovered_exported_class_base
1469        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1470        && is_declaration_name(node)
1471        && let Some(owners) = out_of_line_member_definition_owner(
1472            &ctx.analyzer,
1473            ctx.visibility,
1474            ctx.file,
1475            ctx.source,
1476            node,
1477        )
1478    {
1479        *ctx.raw_match_count += 1;
1480        let mut matched_owner = false;
1481        for (owner_node, owner) in owners.owners {
1482            if same_visible_symbol(&owner, &ctx.spec.target) {
1483                matched_owner = true;
1484                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1485            }
1486        }
1487        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1488            for scope in scopes {
1489                push_hit(scope, ctx);
1490            }
1491        } else if !matched_owner
1492            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1493        {
1494            push_unproven_hit(scope, ctx);
1495        }
1496        return;
1497    }
1498    if !recovered_type
1499        && !recovered_qualified_friend
1500        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1501        && is_declaration_name(node)
1502        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1503    {
1504        *ctx.raw_match_count += 1;
1505        push_type_hit(owner, ctx);
1506        return;
1507    }
1508    // A qualified template-id is represented as a qualified_identifier whose
1509    // name child is the template_type. Resolve the complete qualified
1510    // reference from that inner template node below; handling the outer node
1511    // independently would either lose the qualifier or emit a duplicate,
1512    // wider hit range.
1513    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1514        && matches!(
1515            node.kind(),
1516            "qualified_identifier" | "scoped_type_identifier"
1517        )
1518        && node
1519            .child_by_field_name("name")
1520            .is_some_and(|name| name.kind() == "template_type")
1521    {
1522        return;
1523    }
1524    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1525        *ctx.raw_match_count += 1;
1526        push_unproven_hit(scope, ctx);
1527        return;
1528    }
1529    if !recovered_type
1530        && !is_nested_type_node(node)
1531        && matches!(
1532            node.kind(),
1533            "qualified_identifier" | "scoped_type_identifier"
1534        )
1535        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1536    {
1537        *ctx.raw_match_count += 1;
1538        for scope in scopes {
1539            push_type_hit(scope, ctx);
1540        }
1541        return;
1542    }
1543    if !recovered_type && is_nested_type_node(node) {
1544        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1545            *ctx.raw_match_count += 1;
1546            push_type_hit(hit, ctx);
1547            return;
1548        }
1549        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1550            *ctx.raw_match_count += 1;
1551            push_type_hit(hit, ctx);
1552            return;
1553        }
1554        // A concrete template specialization can be absent from the coarse
1555        // per-file component-name index: that index contains the primary name
1556        // while this recovered child may expose only the malformed template
1557        // leaf.  Resolve the complete enclosing template before applying the
1558        // component prefilter so an exact target candidate can prove this
1559        // reference.  Non-specialization targets retain the cheap gate.
1560        let nested_template = if node.kind() == "template_type" {
1561            Some(node)
1562        } else {
1563            ctx.ancestry.parent(node).filter(|parent| {
1564                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1565            })
1566        };
1567        // Concrete specializations need their nested template-id inspected.
1568        // An ordinary alias application does too when it is itself the scope
1569        // of a member-qualified type, because the enclosing node denotes the
1570        // member rather than the alias. In every other primary/alias shape the
1571        // enclosing structured type owns the hit range; descending would emit
1572        // a duplicate terminal subrange.
1573        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1574            let enclosing_qualified_type_owns_range =
1575                ctx.ancestry.parent(template).is_some_and(|parent| {
1576                    matches!(
1577                        parent.kind(),
1578                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1579                    ) && parent.child_by_field_name("name") == Some(template)
1580                });
1581            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1582                return false;
1583            };
1584            let Some(name) = template_reference_name_node(template) else {
1585                return false;
1586            };
1587            let alias_candidates = ctx
1588                .visibility
1589                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1590                .filter(|candidate| alias_provider.is_type_alias(candidate))
1591                .cloned()
1592                .collect::<Vec<_>>();
1593            let direct_target_alias_visible = !alias_candidates
1594                .iter()
1595                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1596                || ctx.visibility.external_type_candidate_visible_in_context(
1597                    &ctx.analyzer,
1598                    ctx.file,
1599                    &ctx.spec.target,
1600                    template,
1601                );
1602            !enclosing_qualified_type_owns_range
1603                && direct_target_alias_visible
1604                && template_reference_candidates_select_target(
1605                    template,
1606                    &alias_candidates,
1607                    &ctx.analyzer,
1608                    ctx.visibility,
1609                    ctx.file,
1610                    ctx.source,
1611                    &ctx.spec.target,
1612                )
1613        });
1614        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1615            return;
1616        }
1617        if nested_alias_qualifier {
1618            let template = nested_template.expect("a nested alias qualifier has a template node");
1619            *ctx.raw_match_count += 1;
1620            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1621                .unwrap_or_else(|| type_reference_hit_node(template));
1622            push_type_hit(hit, ctx);
1623            return;
1624        }
1625        // For concrete specializations, the visible identifier index can
1626        // already contain the exact template spelling even when lexical
1627        // resolution rejects the recovered child (the child itself has no
1628        // argument list). Select the name as structured evidence;
1629        // `push_type_hit` expands it to the complete written template-id.
1630        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1631            && let Some(template) = nested_template
1632            && template_type_component_preserves_target(
1633                template,
1634                &ctx.visibility
1635                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1636                    .cloned()
1637                    .collect::<Vec<_>>(),
1638                ctx,
1639            )
1640        {
1641            *ctx.raw_match_count += 1;
1642            let hit = template
1643                .child_by_field_name("name")
1644                .filter(|name| name.kind() == "type_identifier")
1645                .unwrap_or(template);
1646            push_type_hit(hit, ctx);
1647            return;
1648        }
1649        if let Some(template) = nested_template
1650            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1651        {
1652            *ctx.raw_match_count += 1;
1653            let hit =
1654                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1655                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1656                        template
1657                            .child_by_field_name("name")
1658                            .filter(|name| name.kind() == "type_identifier")
1659                            .unwrap_or(template)
1660                    } else {
1661                        type_reference_hit_node(template)
1662                    }
1663                });
1664            push_type_hit(hit, ctx);
1665        }
1666        return;
1667    }
1668    if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1669        let direct_target = resolve_qualified_call_target(
1670            call,
1671            node,
1672            &ctx.analyzer,
1673            ctx.visibility,
1674            &ctx.ordinary_type_imports,
1675            ctx.file,
1676            ctx.source,
1677        );
1678        if matches!(direct_target, BareCallTargetResolution::Type(_))
1679            && let LexicalTypeResolution::Resolved {
1680                unit, candidates, ..
1681            } = resolve_type_node_lexically_for_target(
1682                node,
1683                &ctx.analyzer,
1684                ctx.visibility,
1685                &ctx.ordinary_type_imports,
1686                ctx.file,
1687                ctx.source,
1688                &ctx.spec.target,
1689                Some(&ctx.lexical_scope_cache),
1690                ctx.recovered_sentinel_scope(node).as_deref(),
1691            )
1692            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1693        {
1694            *ctx.raw_match_count += 1;
1695            push_resolved_type_hit(node, &candidates, ctx);
1696        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1697            *ctx.raw_match_count += 1;
1698            for scope in scopes {
1699                push_type_hit(scope, ctx);
1700            }
1701        } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1702            *ctx.raw_match_count += 1;
1703            match owner_hit {
1704                TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1705                TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1706                    push_unproven_hit(scope, ctx)
1707                }
1708            }
1709        }
1710        return;
1711    }
1712    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1713        *ctx.raw_match_count += 1;
1714        if proven {
1715            push_type_hit(hit, ctx);
1716        } else {
1717            push_unproven_hit(hit, ctx);
1718        }
1719        return;
1720    }
1721    if !recovered_type
1722        && !recovered_qualified_friend
1723        && !recovered_exported_class_base
1724        && is_declaration_name(node)
1725    {
1726        let mut matched_owner = false;
1727        if let Some(owners) = out_of_line_member_definition_owner(
1728            &ctx.analyzer,
1729            ctx.visibility,
1730            ctx.file,
1731            ctx.source,
1732            node,
1733        ) {
1734            for (owner_node, owner) in owners.owners {
1735                if same_visible_symbol(&owner, &ctx.spec.target) {
1736                    matched_owner = true;
1737                    *ctx.raw_match_count += 1;
1738                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1739                }
1740            }
1741        }
1742        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1743            *ctx.raw_match_count += 1;
1744            for scope in scopes {
1745                push_hit(scope, ctx);
1746            }
1747        } else if !matched_owner
1748            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1749        {
1750            *ctx.raw_match_count += 1;
1751            push_unproven_hit(scope, ctx);
1752        }
1753        return;
1754    }
1755    let hit_node = node;
1756    let text = node_text(hit_node, ctx.source);
1757    let type_resolution = if hit_node.kind() == "template_type"
1758        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1759    {
1760        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1761            resolve_type_node_lexically_for_target(
1762                hit_node,
1763                &ctx.analyzer,
1764                ctx.visibility,
1765                &ctx.ordinary_type_imports,
1766                ctx.file,
1767                ctx.source,
1768                &ctx.spec.target,
1769                Some(&ctx.lexical_scope_cache),
1770                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1771            )
1772        })
1773    } else {
1774        resolve_type_node_lexically_for_target(
1775            hit_node,
1776            &ctx.analyzer,
1777            ctx.visibility,
1778            &ctx.ordinary_type_imports,
1779            ctx.file,
1780            ctx.source,
1781            &ctx.spec.target,
1782            Some(&ctx.lexical_scope_cache),
1783            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1784        )
1785    };
1786    // When the qualifier's template arguments decide the owner, the resolution
1787    // above already looked the member up in that owner. The target-guided
1788    // fallbacks below read the qualifier without its arguments and would
1789    // prove the primary template's member instead, so they do not run; an
1790    // owner the arguments cannot decide leaves the reference unproven
1791    // (#6902).
1792    match select_qualifier_owner(
1793        node,
1794        is_globally_qualified_cpp_name(node),
1795        &ctx.analyzer,
1796        ctx.visibility,
1797        &ctx.ordinary_type_imports,
1798        ctx.file,
1799        ctx.source,
1800    ) {
1801        QualifierOwnerSelection::Unchanged => {}
1802        QualifierOwnerSelection::Selected(_) => {
1803            if !matches!(
1804                &type_resolution,
1805                LexicalTypeResolution::Resolved { unit, candidates, .. }
1806                    if type_resolution_matches_target(node, unit, candidates, ctx)
1807            ) {
1808                return;
1809            }
1810        }
1811        QualifierOwnerSelection::Ambiguous => {
1812            // Only the member the terminal names depends on the undecided
1813            // owner; a qualifier or alias target keeps its ordinary handling.
1814            if let Some(terminal) = cpp_name_component_nodes(node)
1815                .and_then(|components| components.last().copied())
1816                .filter(|terminal| node_text(*terminal, ctx.source) == ctx.spec.target.identifier())
1817            {
1818                *ctx.raw_match_count += 1;
1819                push_unproven_hit(terminal, ctx);
1820                return;
1821            }
1822        }
1823    }
1824    match type_resolution {
1825        LexicalTypeResolution::Resolved {
1826            unit, candidates, ..
1827        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1828            *ctx.raw_match_count += 1;
1829            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1830                for scope in scopes {
1831                    push_type_hit(scope, ctx);
1832                }
1833            } else {
1834                push_resolved_type_hit(hit_node, &candidates, ctx);
1835            }
1836            return;
1837        }
1838        LexicalTypeResolution::Resolved {
1839            unit: _,
1840            candidates,
1841            ..
1842        } => {
1843            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1844                *ctx.raw_match_count += 1;
1845                for scope in scopes {
1846                    push_type_hit(scope, ctx);
1847                }
1848            } else if let Some(hit) =
1849                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1850            {
1851                *ctx.raw_match_count += 1;
1852                push_unproven_hit(hit, ctx);
1853            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1854                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1855            {
1856                *ctx.raw_match_count += 1;
1857                push_type_hit(leaf, ctx);
1858            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1859                *ctx.raw_match_count += 1;
1860                push_unproven_hit(leaf, ctx);
1861            }
1862            return;
1863        }
1864        LexicalTypeResolution::Ambiguous => {
1865            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1866                *ctx.raw_match_count += 1;
1867                for scope in scopes {
1868                    push_type_hit(scope, ctx);
1869                }
1870            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1871                *ctx.raw_match_count += 1;
1872                push_unproven_hit(leaf, ctx);
1873            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1874                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1875                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1876            {
1877                *ctx.raw_match_count += 1;
1878                push_type_hit(leaf, ctx);
1879            } else if guarded_alias_alternatives_include_target(hit_node, ctx) {
1880                *ctx.raw_match_count += 1;
1881                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1882            }
1883            return;
1884        }
1885        LexicalTypeResolution::Missing => {
1886            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1887                &ctx.analyzer,
1888                ctx.file,
1889                hit_node,
1890                ctx.source,
1891            ) && same_visible_symbol(&unit, &ctx.spec.target)
1892            {
1893                *ctx.raw_match_count += 1;
1894                push_type_hit(hit_node, ctx);
1895                return;
1896            }
1897            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1898                *ctx.raw_match_count += 1;
1899                push_unproven_hit(leaf, ctx);
1900                return;
1901            }
1902            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1903                *ctx.raw_match_count += 1;
1904                push_unproven_hit(leaf, ctx);
1905                return;
1906            }
1907            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1908                *ctx.raw_match_count += 1;
1909                push_type_hit(leaf, ctx);
1910                return;
1911            }
1912            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1913                hit_node,
1914                &ctx.analyzer,
1915                ctx.visibility,
1916                ctx.file,
1917                ctx.source,
1918                &ctx.spec.target,
1919            );
1920            let raw_matches = matches!(
1921                raw_resolution,
1922                LexicalTypeResolution::Resolved {
1923                    ref unit,
1924                    ref candidates,
1925                    ..
1926                } if type_resolution_identifies_unit_target(
1927                    hit_node,
1928                    unit,
1929                    candidates,
1930                    &ctx.spec.target,
1931                    ctx,
1932                )
1933            );
1934            if raw_matches
1935                || type_node_has_exact_target_identity_without_visibility(
1936                    hit_node,
1937                    &ctx.analyzer,
1938                    ctx.visibility,
1939                    ctx.file,
1940                    ctx.source,
1941                    &ctx.spec.target,
1942                )
1943            {
1944                *ctx.raw_match_count += 1;
1945                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1946                return;
1947            }
1948        }
1949    }
1950    // A class-owned alias can be qualified by an owner made visible through a
1951    // namespace import, including an import in an earlier reopening of the
1952    // same unnamed namespace. Resolve that owner before requiring an
1953    // enclosing class for inherited nested-type lookup.
1954    if ctx
1955        .visibility
1956        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1957    {
1958        *ctx.raw_match_count += 1;
1959        push_type_hit(type_reference_hit_node(hit_node), ctx);
1960        return;
1961    }
1962    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1963        *ctx.raw_match_count += 1;
1964        for scope in scopes {
1965            push_type_hit(scope, ctx);
1966        }
1967        return;
1968    }
1969    if !name_mentions(text, &ctx.spec.member_name) {
1970        return;
1971    }
1972    *ctx.raw_match_count += 1;
1973    if !ctx.visibility.external_type_candidate_visible_in_context(
1974        &ctx.analyzer,
1975        ctx.file,
1976        &ctx.spec.target,
1977        hit_node,
1978    ) {
1979        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1980        if type_reference_resolves_away_from_target(unproven, ctx) {
1981            return;
1982        }
1983        push_unproven_hit(unproven, ctx);
1984    }
1985}
1986
1987fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1988    for reference in object_macro_replacement_type_references(node, ctx.source) {
1989        for component_count in 1..=reference.components.len() {
1990            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1991                node,
1992                &reference.components[..component_count],
1993                reference.global,
1994                &ctx.analyzer,
1995                ctx.visibility,
1996                &ctx.ordinary_type_imports,
1997                ctx.file,
1998                ctx.source,
1999                &ctx.spec.target,
2000                false,
2001                Some(&ctx.lexical_scope_cache),
2002            );
2003            let matches_target = match resolution {
2004                LexicalTypeResolution::Resolved {
2005                    unit, candidates, ..
2006                } => {
2007                    same_visible_symbol(&unit, &ctx.spec.target)
2008                        || candidates
2009                            .iter()
2010                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2011                }
2012                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
2013                    &ctx.analyzer,
2014                    ctx.visibility,
2015                    ctx.file,
2016                    &reference.components[..component_count],
2017                )
2018                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
2019                LexicalTypeResolution::Ambiguous => false,
2020            };
2021            if !matches_target {
2022                continue;
2023            }
2024            let range = &reference.component_ranges[component_count - 1];
2025            *ctx.raw_match_count += 1;
2026            push_type_hit_range(node, range.start, range.end, ctx);
2027        }
2028    }
2029}
2030
2031fn target_guided_compatible_foreign_import_type_leaf<'tree>(
2032    node: Node<'tree>,
2033    ctx: &ScanCtx<'_>,
2034) -> Option<Node<'tree>> {
2035    let (components, global) = type_reference_components(node, ctx.source)?;
2036    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
2037        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
2038            node,
2039            &ctx.analyzer,
2040            ctx.visibility,
2041            ctx.file,
2042            ctx.source,
2043            false,
2044            false,
2045            Some(&ctx.lexical_scope_cache),
2046        ) {
2047            LexicalScopeResolution::Resolved(scope) => Some(scope),
2048            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
2049        }
2050    })?;
2051    let OrdinaryTypeImportResolution::Resolved { target, .. } =
2052        compatible_foreign_type_import_resolution(
2053            node,
2054            &components,
2055            global,
2056            &ctx.analyzer,
2057            ctx.visibility,
2058            &ctx.ordinary_type_imports,
2059            ctx.file,
2060            ctx.source,
2061            &lexical_scope,
2062            Some(&ctx.spec.target),
2063        )
2064    else {
2065        return None;
2066    };
2067    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2068}
2069
2070/// Resolve `Owner::Nested` when complete type lookup cannot express the owner
2071/// as its first qualified component. This occurs when a using-directive
2072/// imports `Owner` and when inherited-type lookup injects it. Resolve the
2073/// owner prefix independently, then return the terminal node after that owner
2074/// proves the target's direct nested declaration; hit insertion expands it to
2075/// the complete written type.
2076fn target_guided_nested_type_terminal_hit<'tree>(
2077    node: Node<'tree>,
2078    ctx: &ScanCtx<'_>,
2079) -> Option<Node<'tree>> {
2080    // Returns a node of the caller's tree lifetime, which the context-scoped
2081    // ancestry index cannot name; this is one question per call.
2082    let qualified = node.parent().filter(|parent| {
2083        matches!(
2084            parent.kind(),
2085            "qualified_identifier" | "scoped_type_identifier"
2086        ) && parent.child_by_field_name("name") == Some(node)
2087    })?;
2088    let mut complete = qualified;
2089    while let Some(parent) = complete.parent().filter(|parent| {
2090        matches!(
2091            parent.kind(),
2092            "qualified_identifier" | "scoped_type_identifier"
2093        )
2094    }) {
2095        complete = parent;
2096    }
2097    let owner = qualified_owner_components(complete, ctx.source)?;
2098
2099    if let LexicalTypeResolution::Resolved {
2100        unit, candidates, ..
2101    } = resolve_type_node_lexically_for_target(
2102        complete,
2103        &ctx.analyzer,
2104        ctx.visibility,
2105        &ctx.ordinary_type_imports,
2106        ctx.file,
2107        ctx.source,
2108        &ctx.spec.target,
2109        Some(&ctx.lexical_scope_cache),
2110        ctx.recovered_sentinel_scope(complete).as_deref(),
2111    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2112        && ctx
2113            .analyzer
2114            .parent_of(&ctx.spec.target)
2115            .is_some_and(|parent| parent.is_class())
2116        && !ctx
2117            .analyzer
2118            .type_alias_provider()
2119            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2120        && is_namespace_scope_type_reference(complete)
2121        && namespace_import_can_name_nested_target(complete, ctx)
2122    {
2123        return Some(node);
2124    }
2125    // The owner-spelling paths below compare the qualifier's names without
2126    // its template arguments, which only names the right owner when those
2127    // arguments select the primary template (#6902).
2128    if !matches!(
2129        select_qualifier_owner(
2130            complete,
2131            owner.global,
2132            &ctx.analyzer,
2133            ctx.visibility,
2134            &ctx.ordinary_type_imports,
2135            ctx.file,
2136            ctx.source,
2137        ),
2138        QualifierOwnerSelection::Unchanged
2139    ) {
2140        return None;
2141    }
2142
2143    let target_owner = ctx
2144        .analyzer
2145        .parent_of(&ctx.spec.target)
2146        .filter(CodeUnit::is_class);
2147    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2148        let target_components = canonical_cpp_scope_components(target_owner);
2149        if owner.global {
2150            target_components == owner.names
2151        } else {
2152            target_components.ends_with(&owner.names)
2153        }
2154    });
2155    let target_member_visible = |target_owner: &CodeUnit| {
2156        ctx.visibility
2157            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2158            .into_iter()
2159            .any(|candidate| {
2160                same_visible_symbol(candidate, &ctx.spec.target)
2161                    && (ctx
2162                        .visibility
2163                        .external_type_candidate_guard_compatible_in_context(
2164                            &ctx.analyzer,
2165                            ctx.file,
2166                            candidate,
2167                            complete,
2168                        )
2169                        || (ctx
2170                            .visibility
2171                            .is_exhaustive_same_fqn_type_declaration_family(
2172                                &ctx.analyzer,
2173                                ctx.file,
2174                                candidate,
2175                            )
2176                            && ctx.visibility.external_type_candidate_visible_in_context(
2177                                &ctx.analyzer,
2178                                ctx.file,
2179                                candidate,
2180                                complete,
2181                            )))
2182            })
2183    };
2184
2185    if owner_spelling_can_name_target
2186        && (is_namespace_scope_type_reference(complete)
2187            || (is_compound_type_reference(complete)
2188                && namespace_import_can_name_nested_target(complete, ctx)))
2189        && let Some(target_owner) = target_owner.as_ref()
2190        && let LexicalTypeResolution::Resolved { unit, .. } =
2191            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2192                complete,
2193                &owner.names,
2194                owner.global,
2195                &ctx.analyzer,
2196                ctx.visibility,
2197                &ctx.ordinary_type_imports,
2198                ctx.file,
2199                ctx.source,
2200                Some(&ctx.lexical_scope_cache),
2201            )
2202        && ctx
2203            .visibility
2204            .same_template_owner_identity(&unit, target_owner)
2205        && target_member_visible(target_owner)
2206    {
2207        return Some(node);
2208    }
2209
2210    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2211    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2212    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2213        &ctx.analyzer,
2214        ctx.file,
2215        &owner.names,
2216        owner.global,
2217        &lexical_scope,
2218    );
2219    let owner_unit = match owner_resolution {
2220        LexicalTypeResolution::Resolved { unit, .. } => unit,
2221        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2222            ctx.visibility.inherited_injected_class_owner(
2223                &ctx.analyzer,
2224                ctx.file,
2225                &enclosing_owner,
2226                owner.names.first()?,
2227            )?
2228        }
2229        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2230    };
2231    let name = node_text(node, ctx.source);
2232    ctx.visibility
2233        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2234        .into_iter()
2235        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2236        .then_some(complete)
2237}
2238
2239fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2240    // Namespace-scope nested classes need a terminal edge when the complete
2241    // qualified node is the only structured evidence (as in #2213). In
2242    // parameters, fields, and callable bodies the enclosing type node already
2243    // owns the exact range; adding its terminal would duplicate that edge.
2244    let mut current = node.parent();
2245    while let Some(parent) = current {
2246        match parent.kind() {
2247            "call_expression" | "new_expression" => return true,
2248            "parameter_declaration"
2249            | "optional_parameter_declaration"
2250            | "field_declaration"
2251            | "function_definition"
2252            | "function_declarator"
2253            | "class_specifier"
2254            | "struct_specifier"
2255            | "union_specifier"
2256            | "compound_statement" => return false,
2257            "declaration" => {
2258                let mut cursor = parent.walk();
2259                if parent
2260                    .named_children(&mut cursor)
2261                    .any(|child| child.kind() == "function_declarator")
2262                {
2263                    return false;
2264                }
2265            }
2266            _ => {}
2267        }
2268        current = parent.parent();
2269    }
2270    true
2271}
2272
2273fn is_compound_type_reference(node: Node<'_>) -> bool {
2274    let mut current = node.parent();
2275    while let Some(parent) = current {
2276        match parent.kind() {
2277            "compound_statement" => return true,
2278            "namespace_definition" | "translation_unit" => return false,
2279            _ => current = parent.parent(),
2280        }
2281    }
2282    false
2283}
2284
2285fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2286    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2287        return false;
2288    };
2289    effective_using_bindings_for_name(
2290        ctx.visibility,
2291        &ctx.ordinary_type_imports,
2292        ctx.file,
2293        node,
2294        ctx.source,
2295        target_owner.identifier(),
2296    )
2297    .iter()
2298    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2299}
2300
2301/// Recover the class-template qualifier in a dependent leading return type of
2302/// an out-of-line member definition.
2303///
2304/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2305/// `Owner`: one in the return type and one in the callable declarator. The
2306/// latter already has authoritative indexed owner resolution. Reuse that
2307/// structured owner evidence for the former when both template names agree;
2308/// resolving `Owner<T>::Alias` as one type would instead select terminal
2309/// `Alias` and discard the qualifier.
2310fn out_of_line_dependent_return_template_owner<'tree>(
2311    node: Node<'tree>,
2312    ctx: &ScanCtx<'_>,
2313) -> Option<Node<'tree>> {
2314    if node.kind() != "template_type" {
2315        return None;
2316    }
2317    let template_name = template_reference_name_node(node)?;
2318    let mut scope = node;
2319    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2320        matches!(
2321            parent.kind(),
2322            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2323        ) && parent.child_by_field_name("name") == Some(scope)
2324    }) {
2325        scope = parent;
2326    }
2327    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2328        matches!(
2329            parent.kind(),
2330            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2331        ) && parent.child_by_field_name("scope") == Some(scope)
2332    })?;
2333    let mut function = ctx.ancestry.parent(qualified);
2334    let function = loop {
2335        let candidate = function?;
2336        if candidate.kind() == "function_definition" {
2337            break candidate;
2338        }
2339        function = ctx.ancestry.parent(candidate);
2340    };
2341    let return_type = function.child_by_field_name("type")?;
2342    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2343        return None;
2344    }
2345    let declarator_name = function
2346        .child_by_field_name("declarator")
2347        .and_then(declarator_name_node)?;
2348    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2349        return None;
2350    }
2351    let resolved_owner_matches = out_of_line_member_definition_owner(
2352        &ctx.analyzer,
2353        ctx.visibility,
2354        ctx.file,
2355        ctx.source,
2356        declarator_name,
2357    )
2358    .is_some_and(|owners| {
2359        owners
2360            .owners
2361            .iter()
2362            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2363    });
2364    let indexed_owner_matches =
2365        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2366    let target_guided_owner_matches =
2367        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2368    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2369        .then_some(template_name)
2370}
2371
2372/// Use the indexed callable identity when a malformed namespace sentinel
2373/// prevents lexical lookup of an out-of-line template owner.
2374fn indexed_out_of_line_template_owner_hit<'tree>(
2375    node: Node<'tree>,
2376    ctx: &ScanCtx<'_>,
2377) -> Option<Node<'tree>> {
2378    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2379        || !is_declaration_name(node)
2380    {
2381        return None;
2382    }
2383    let mut function = ctx.ancestry.parent(node);
2384    let function = loop {
2385        let candidate = function?;
2386        if candidate.kind() == "function_definition" {
2387            break candidate;
2388        }
2389        function = ctx.ancestry.parent(candidate);
2390    };
2391    if function
2392        .child_by_field_name("declarator")
2393        .and_then(declarator_name_node)
2394        != Some(node)
2395    {
2396        return None;
2397    }
2398    let target = physically_visible_type_target(ctx)?;
2399    let qualified = qualified_owner_components(node, ctx.source)?;
2400    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2401        return None;
2402    }
2403    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2404        != canonical_cpp_scope_components(target)
2405    {
2406        return None;
2407    }
2408    let owner = qualified.nodes.last().copied()?;
2409    let template = if owner.kind() == "template_type" {
2410        owner
2411    } else {
2412        owner
2413            .parent()
2414            .filter(|parent| parent.kind() == "template_type")?
2415    };
2416    template_reference_name_node(template)
2417}
2418
2419fn push_guarded_owner_hit(
2420    owner_node: Node<'_>,
2421    owner: &CodeUnit,
2422    reference: Node<'_>,
2423    ctx: &mut ScanCtx<'_>,
2424) {
2425    if ctx
2426        .visibility
2427        .external_type_candidate_guard_compatible_in_context(
2428            &ctx.analyzer,
2429            ctx.file,
2430            owner,
2431            reference,
2432        )
2433    {
2434        push_hit(owner_node, ctx);
2435    } else {
2436        push_unproven_hit(owner_node, ctx);
2437    }
2438}
2439
2440/// Preserve a direct template-alias reference when a macro namespace sentinel
2441/// makes tree-sitter drop the first source path component.
2442fn target_guided_alias_template_reference<'tree>(
2443    node: Node<'tree>,
2444    ctx: &ScanCtx<'_>,
2445) -> Option<(Node<'tree>, bool)> {
2446    let alias_provider = ctx.analyzer.type_alias_provider()?;
2447    if !matches!(
2448        node.kind(),
2449        "qualified_identifier" | "scoped_type_identifier"
2450    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2451    {
2452        return None;
2453    }
2454    cpp_template_reference_arguments(node, ctx.source)?;
2455    let (components, global) = type_reference_components(node, ctx.source)?;
2456    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2457        return None;
2458    }
2459    let target = physically_visible_type_target(ctx)?;
2460    let target_components = canonical_cpp_scope_components(target);
2461    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2462    let path_matches = if global {
2463        components == target_components
2464            || (!parser_namespace.is_empty()
2465                && target_components.starts_with(&parser_namespace)
2466                && target_components[parser_namespace.len()..] == components)
2467    } else {
2468        let lexical_scope = match enclosing_lexical_scope_components(
2469            node,
2470            &ctx.analyzer,
2471            ctx.visibility,
2472            ctx.file,
2473            ctx.source,
2474        ) {
2475            LexicalScopeResolution::Resolved(scope) => scope,
2476            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2477        };
2478        lexical_component_tiers(&components, false, &lexical_scope)
2479            .any(|scope| scope == target_components)
2480    };
2481    let scoped_candidates = ctx
2482        .visibility
2483        .visible_identifier_candidates(ctx.file, target.identifier())
2484        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2485        .collect::<Vec<_>>();
2486    if !path_matches
2487        || scoped_candidates.is_empty()
2488        || scoped_candidates
2489            .iter()
2490            .any(|candidate| !same_visible_symbol(candidate, target))
2491        || !ctx.visibility.structured_alias_primary_preserves_target(
2492            &ctx.analyzer,
2493            ctx.file,
2494            target,
2495            target,
2496        )
2497    {
2498        return None;
2499    }
2500    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2501        &ctx.analyzer,
2502        ctx.file,
2503        target,
2504        node,
2505    );
2506    Some((node, proven))
2507}
2508
2509/// Tree-sitter can split a macro-qualified member return type into a phantom
2510/// field followed by the real function definition.  The declaration visitor
2511/// discards that phantom field, but its declarator token remains a semantic
2512/// type reference. Resolve it from the recovered or indexed class scope so an
2513/// enclosing-class alias remains distinguishable from same-spelled siblings.
2514fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2515    let name = node_text(return_type, ctx.source);
2516    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2517        return;
2518    }
2519    if physically_visible_type_target(ctx).is_some()
2520        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2521        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2522        && ctx.visibility.external_type_candidate_visible_in_context(
2523            &ctx.analyzer,
2524            ctx.file,
2525            &ctx.spec.target,
2526            return_type,
2527        )
2528    {
2529        *ctx.raw_match_count += 1;
2530        push_type_hit(return_type, ctx);
2531        return;
2532    }
2533    let Some(scope) = ctx
2534        .recovered_sentinel_scope(return_type)
2535        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2536    else {
2537        return;
2538    };
2539    let components = [name.to_string()];
2540    let resolution = ctx.visibility.resolve_type_components_lexically(
2541        &ctx.analyzer,
2542        ctx.file,
2543        &components,
2544        false,
2545        &scope,
2546    );
2547    if let LexicalTypeResolution::Resolved {
2548        unit, candidates, ..
2549    } = resolution
2550        && (same_visible_symbol(&unit, &ctx.spec.target)
2551            || candidates
2552                .iter()
2553                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2554    {
2555        *ctx.raw_match_count += 1;
2556        push_type_hit(return_type, ctx);
2557    }
2558}
2559
2560/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2561/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2562/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2563/// than a type node; ordinary type-reference traversal therefore skips it.
2564fn member_pointer_owner_components<'tree>(
2565    node: Node<'tree>,
2566    source: &str,
2567) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2568    if node.kind() != "qualified_identifier" {
2569        return None;
2570    }
2571    let declarator = node.child_by_field_name("name")?;
2572    // brokk-tree-sitter-cpp 0.24.7 names a member pointer with the pointer
2573    // declarator of its context: `pointer_declarator` in declarations,
2574    // parameters and fields, `pointer_type_declarator` in typedefs, and
2575    // `abstract_pointer_declarator` in `R (Owner::*)(...)`.
2576    if !matches!(
2577        declarator.kind(),
2578        "pointer_declarator" | "pointer_type_declarator" | "abstract_pointer_declarator"
2579    ) {
2580        return None;
2581    }
2582    let scope = node.child_by_field_name("scope")?;
2583    // Keep this check entirely structural.  C and C++ share the
2584    // `qualified_identifier` node shape for some recovered declarations, but
2585    // only the C++ member-pointer form has an actual `::` grammar child
2586    // between the owner scope and pointer declarator.  Looking at the source
2587    // slice here would make a recovered C declarator look like a C++ owner
2588    // merely because its bytes happen to contain the same punctuation.
2589    let mut saw_scope = false;
2590    let has_scope_separator = (0..node.child_count()).any(|index| {
2591        let Some(child) = node.child(index) else {
2592            return false;
2593        };
2594        if same_node(child, scope) {
2595            saw_scope = true;
2596            return false;
2597        }
2598        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2599    });
2600    if !has_scope_separator {
2601        return None;
2602    }
2603    let mut nodes = cpp_name_component_nodes(scope)?;
2604    let mut outer = node;
2605    while let Some(parent) = outer.parent()
2606        && parent.kind() == "qualified_identifier"
2607        && parent.child_by_field_name("name") == Some(outer)
2608    {
2609        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2610        prefix.append(&mut nodes);
2611        nodes = prefix;
2612        outer = parent;
2613    }
2614    let names = nodes
2615        .iter()
2616        .map(|component| node_text(*component, source).to_string())
2617        .collect();
2618    Some((
2619        QualifiedOwnerComponents {
2620            nodes,
2621            names,
2622            global: is_globally_qualified_cpp_name(outer),
2623        },
2624        outer,
2625    ))
2626}
2627
2628fn member_pointer_alias_owner_prefix_matches(
2629    node: Node<'_>,
2630    owner: &QualifiedOwnerComponents<'_>,
2631    ctx: &ScanCtx<'_>,
2632) -> bool {
2633    let Some((_, owner_prefix)) = owner.names.split_last() else {
2634        return false;
2635    };
2636    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2637        return false;
2638    };
2639    let recovered_scope = ctx.recovered_sentinel_scope(node);
2640    let resolution = if let Some(recovered_scope) = recovered_scope {
2641        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2642            node,
2643            owner_prefix,
2644            owner.global,
2645            &ctx.analyzer,
2646            ctx.visibility,
2647            &ctx.ordinary_type_imports,
2648            ctx.file,
2649            ctx.source,
2650            &parent,
2651            false,
2652            &recovered_scope,
2653        )
2654    } else {
2655        resolve_type_components_lexically_at_for_target_with_scope_cache(
2656            node,
2657            owner_prefix,
2658            owner.global,
2659            &ctx.analyzer,
2660            ctx.visibility,
2661            &ctx.ordinary_type_imports,
2662            ctx.file,
2663            ctx.source,
2664            &parent,
2665            false,
2666            Some(&ctx.lexical_scope_cache),
2667        )
2668    };
2669    let LexicalTypeResolution::Resolved {
2670        unit, candidates, ..
2671    } = resolution
2672    else {
2673        return false;
2674    };
2675    same_member_pointer_owner_identity(&unit, &parent)
2676        || candidates
2677            .iter()
2678            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2679}
2680
2681fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2682    same_visible_symbol(left, right)
2683        || (left.kind() == right.kind()
2684            && left.fq_name() == right.fq_name()
2685            && left.source() == right.source())
2686}
2687
2688/// Resolve a template type nested in a qualified identifier against a concrete
2689/// type target. The target-guided lexical path intentionally applies a
2690/// structured candidate prefilter; that prefilter cannot see a partial
2691/// specialization until the template arguments have selected it. Resolve the
2692/// complete qualified primary first, then apply the parsed arguments and
2693/// retain the result only when it is the requested target.
2694fn resolve_nested_template_type_for_target(
2695    node: Node<'_>,
2696    ctx: &ScanCtx<'_>,
2697) -> Option<LexicalTypeResolution> {
2698    let reference_node = node
2699        .parent()
2700        .filter(|parent| {
2701            parent.kind() == "qualified_identifier"
2702                && parent.child_by_field_name("name") == Some(node)
2703        })
2704        .unwrap_or(node);
2705    let target_resolution = resolve_type_node_lexically_for_target(
2706        reference_node,
2707        &ctx.analyzer,
2708        ctx.visibility,
2709        &ctx.ordinary_type_imports,
2710        ctx.file,
2711        ctx.source,
2712        &ctx.spec.target,
2713        Some(&ctx.lexical_scope_cache),
2714        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2715    );
2716    if let LexicalTypeResolution::Resolved {
2717        unit, candidates, ..
2718    } = target_resolution
2719        && template_reference_candidates_select_target(
2720            reference_node,
2721            &candidates,
2722            &ctx.analyzer,
2723            ctx.visibility,
2724            ctx.file,
2725            ctx.source,
2726            &ctx.spec.target,
2727        )
2728    {
2729        return Some(LexicalTypeResolution::Resolved {
2730            unit,
2731            components: Vec::new(),
2732            candidates,
2733        });
2734    }
2735
2736    let normal_resolution = resolve_type_node_lexically(
2737        reference_node,
2738        &ctx.analyzer,
2739        ctx.visibility,
2740        &ctx.ordinary_type_imports,
2741        ctx.file,
2742        ctx.source,
2743    );
2744    let LexicalTypeResolution::Resolved {
2745        unit,
2746        components,
2747        candidates,
2748    } = normal_resolution
2749    else {
2750        return None;
2751    };
2752    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2753    let specialized = ctx
2754        .visibility
2755        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2756        .ok()
2757        .unwrap_or(unit);
2758    (same_visible_symbol(&specialized, &ctx.spec.target)
2759        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2760    .then_some(LexicalTypeResolution::Resolved {
2761        unit: specialized,
2762        components,
2763        candidates,
2764    })
2765}
2766
2767fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2768    let mut reference = node;
2769    while let Some(parent) = ctx.ancestry.parent(reference) {
2770        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2771            && parent.child_by_field_name("name") == Some(reference)
2772            || matches!(
2773                parent.kind(),
2774                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2775            ) && (parent.child_by_field_name("scope") == Some(reference)
2776                || parent.child_by_field_name("name") == Some(reference));
2777        if !owns_component {
2778            break;
2779        }
2780        reference = parent;
2781    }
2782    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2783        return false;
2784    };
2785    if !global
2786        && components.len() > 1
2787        && let Some(alias_type) = local_receiver_alias_type_node(reference, &components[0], ctx)
2788    {
2789        let Ok(Some(owner)) = resolve_receiver_type_node(alias_type, ctx) else {
2790            return false;
2791        };
2792        let mut redirected = canonical_cpp_scope_components(&owner);
2793        redirected.extend_from_slice(&components[1..]);
2794        return type_reference_component_list_may_name_target(
2795            &redirected,
2796            true,
2797            &ctx.analyzer,
2798            ctx.visibility,
2799            ctx.file,
2800            &ctx.spec.target,
2801            &ctx.type_reference_component_names,
2802        );
2803    }
2804    type_reference_component_list_may_name_target(
2805        &components,
2806        global,
2807        &ctx.analyzer,
2808        ctx.visibility,
2809        ctx.file,
2810        &ctx.spec.target,
2811        &ctx.type_reference_component_names,
2812    )
2813}
2814
2815fn type_reference_component_list_may_name_target(
2816    components: &[String],
2817    global: bool,
2818    analyzer: &CppGraphSource<'_>,
2819    visibility: &VisibilityIndex<'_>,
2820    file: &ProjectFile,
2821    target: &CodeUnit,
2822    reference_component_names: &HashSet<String>,
2823) -> bool {
2824    if components.iter().any(|component| {
2825        visibility.type_reference_component_directly_names_target(component, target)
2826    }) {
2827        return true;
2828    }
2829    if components.iter().any(|component| {
2830        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2831    }) {
2832        return true;
2833    }
2834    if components.len() > 1 {
2835        return visibility.qualified_alias_reference_may_reach_target(
2836            analyzer, file, components, global, target,
2837        );
2838    }
2839    components
2840        .iter()
2841        .any(|component| reference_component_names.contains(component))
2842}
2843
2844/// Fast structured admission before a type scan builds its parent and lexical
2845/// indexes. Most inverse candidates merely include the target's header and do
2846/// not contain a reference spelling; the full stateful scan has no work in
2847/// those files. Only maximal grammar type names are tested so a common nested
2848/// alias terminal such as `type` is considered together with its owner.
2849fn file_may_reference_type_target(
2850    root: Node<'_>,
2851    source: &str,
2852    analyzer: &CppGraphSource<'_>,
2853    visibility: &VisibilityIndex<'_>,
2854    file: &ProjectFile,
2855    target: &CodeUnit,
2856    reference_component_names: &HashSet<String>,
2857) -> bool {
2858    visit_file_type_reference_spellings(root, source, |components, global| {
2859        type_reference_component_list_may_name_target(
2860            components,
2861            global,
2862            analyzer,
2863            visibility,
2864            file,
2865            target,
2866            reference_component_names,
2867        )
2868    })
2869}
2870
2871fn visit_file_type_reference_spellings(
2872    root: Node<'_>,
2873    source: &str,
2874    mut visit: impl FnMut(&[String], bool) -> bool,
2875) -> bool {
2876    let mut stopped = false;
2877    walk_named_tree_preorder(root, true, |node| {
2878        if node.kind() == "comment" {
2879            return WalkControl::SkipChildren;
2880        }
2881        if node.kind() == "preproc_arg" {
2882            stopped = object_macro_replacement_type_references(node, source)
2883                .into_iter()
2884                .any(|reference| visit(&reference.components, reference.global));
2885            return if stopped {
2886                WalkControl::Break
2887            } else {
2888                WalkControl::SkipChildren
2889            };
2890        }
2891        if node.kind() == "field_declaration"
2892            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2893        {
2894            let components = [node_text(return_type, source).to_string()];
2895            stopped = visit(&components, false);
2896            if stopped {
2897                return WalkControl::Break;
2898            }
2899        }
2900        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2901            for candidate in [node, type_node] {
2902                let Some((components, global)) = type_reference_components(candidate, source)
2903                else {
2904                    continue;
2905                };
2906                stopped = visit(&components, global);
2907                if stopped {
2908                    return WalkControl::Break;
2909                }
2910            }
2911        }
2912        let Some((components, global)) = type_reference_components(node, source) else {
2913            return WalkControl::Continue;
2914        };
2915        if node.parent().is_some_and(|parent| {
2916            (matches!(parent.kind(), "template_type" | "template_function")
2917                && parent.child_by_field_name("name") == Some(node))
2918                || (matches!(
2919                    parent.kind(),
2920                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2921                ) && (parent.child_by_field_name("scope") == Some(node)
2922                    || parent.child_by_field_name("name") == Some(node)))
2923        }) {
2924            return WalkControl::Continue;
2925        }
2926        stopped = visit(&components, global);
2927        if stopped {
2928            WalkControl::Break
2929        } else {
2930            WalkControl::Continue
2931        }
2932    });
2933    stopped
2934}
2935
2936/// Whether this file decides the reference against the scan target.
2937///
2938/// The last resort of type admission has no structured resolution left: it
2939/// only knows that the spelling mentions the target's name and that the target
2940/// itself is not visible here. That is undecidable when nothing else declares
2941/// the name, but it is a proven negative when the file already makes another
2942/// declaration of the same name visible, because unqualified lookup binds the
2943/// reference to that declaration and the include closure never reaches the
2944/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2945/// the same question before it resolves; asking it again here keeps the
2946/// inverse from admitting a site that forward resolution has already decided
2947/// (#2916).
2948fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2949    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2950        return false;
2951    };
2952    let terminal = components
2953        .last()
2954        .expect("type reference components are non-empty");
2955    if terminal != ctx.spec.target.identifier() {
2956        return false;
2957    }
2958    // A concrete specialization is selected from the template arguments, not
2959    // from the visible primary name, so the name-level candidate set cannot
2960    // decide it. `resolve_type_node_lexically_for_target` makes the same
2961    // exception before its own structured prefilter.
2962    if cpp_template_reference_arguments(node, ctx.source).is_some()
2963        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2964    {
2965        return false;
2966    }
2967    // One C++ type reached through a forward declaration in one header and its
2968    // definition in another is two CodeUnits with different signatures and one
2969    // qualified name. A file that sees either of them sees the target's type,
2970    // so only a candidate under a different qualified name decides the
2971    // reference against it. This is what separates the decoy in an unrelated
2972    // namespace from a redeclaration of the target itself.
2973    if ctx
2974        .visibility
2975        .visible_identifier_candidates(ctx.file, terminal)
2976        .any(|candidate| {
2977            candidate.kind() == ctx.spec.target.kind()
2978                && candidate.fq_name() == ctx.spec.target.fq_name()
2979        })
2980    {
2981        return false;
2982    }
2983    !ctx.visibility
2984        .structured_type_reference_may_resolve_to_target(
2985            &ctx.analyzer,
2986            ctx.file,
2987            std::slice::from_ref(terminal),
2988            false,
2989            &[],
2990            &ctx.spec.target,
2991        )
2992}
2993
2994fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2995    let Some(scope) = node.child_by_field_name("scope") else {
2996        return false;
2997    };
2998    let mut pending = vec![scope];
2999    while let Some(candidate) = pending.pop() {
3000        if candidate.kind() == "template_type" {
3001            return true;
3002        }
3003        if matches!(
3004            candidate.kind(),
3005            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3006        ) {
3007            if let Some(scope) = candidate.child_by_field_name("scope") {
3008                pending.push(scope);
3009            }
3010            if let Some(name) = candidate.child_by_field_name("name") {
3011                pending.push(name);
3012            }
3013        }
3014    }
3015    false
3016}
3017
3018/// Whether an undecided reference to a conditional alias family keeps the
3019/// target as one of its alternatives. Guards that select no arm leave every
3020/// compatible arm, and the type it names, a candidate rather than proof
3021/// (#6902).
3022fn guarded_alias_alternatives_include_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
3023    let Some((components, global)) = type_reference_components(node, ctx.source) else {
3024        return false;
3025    };
3026    // The arms are candidates whose guards need not hold here, so look them up
3027    // without the in-context visibility filter that would drop them.
3028    let lexical_scope = if global {
3029        Vec::new()
3030    } else {
3031        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
3032            node,
3033            &ctx.analyzer,
3034            ctx.visibility,
3035            ctx.file,
3036            ctx.source,
3037            true,
3038            false,
3039            Some(&ctx.lexical_scope_cache),
3040        ) {
3041            LexicalScopeResolution::Resolved(scope) => scope,
3042            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return false,
3043        }
3044    };
3045    let LexicalTypeResolution::Resolved { candidates, .. } = ctx
3046        .visibility
3047        .resolve_type_components_lexically_for_forward(
3048            &ctx.analyzer,
3049            ctx.file,
3050            &components,
3051            global,
3052            &lexical_scope,
3053        )
3054    else {
3055        return false;
3056    };
3057    let Some(GuardedAliasSelection::Alternatives(alternatives)) = ctx
3058        .visibility
3059        .guarded_alias_selection_at_reference(&ctx.analyzer, ctx.file, node, &candidates)
3060    else {
3061        return false;
3062    };
3063    alternatives
3064        .iter()
3065        .chain(&candidates)
3066        .any(|unit| same_visible_symbol(unit, &ctx.spec.target))
3067}
3068
3069fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
3070    ctx.target_group.iter().find(|target| {
3071        same_logical_symbol(target, &ctx.spec.target)
3072            && ctx.visibility.is_physically_visible(ctx.file, target)
3073    })
3074}
3075
3076/// Preserve the terminal token of a resolver-proven alias construction.
3077/// Ordinary qualified types and template aliases retain their full type range.
3078/// Alias identity and the construction context are proven by the resolver.
3079fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
3080    if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
3081        push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
3082    } else {
3083        push_type_hit(type_reference_hit_node(node), ctx);
3084    }
3085}
3086
3087fn resolved_alias_terminal_node<'tree>(
3088    node: Node<'tree>,
3089    candidates: &[CodeUnit],
3090    ctx: &ScanCtx<'_>,
3091) -> Option<Node<'tree>> {
3092    let [candidate] = candidates else {
3093        return None;
3094    };
3095    let alias_provider = ctx.analyzer.type_alias_provider()?;
3096    let candidate_is_alias = alias_provider.is_type_alias(candidate)
3097        && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
3098    let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
3099        && same_visible_symbol(candidate, &ctx.spec.target);
3100    if !(candidate_is_alias || target_is_alias) {
3101        return None;
3102    }
3103    if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
3104        return None;
3105    }
3106    let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
3107        node,
3108        &ctx.spec.target,
3109        &ctx.analyzer,
3110        ctx.visibility,
3111        ctx.file,
3112        ctx.source,
3113    );
3114    if reference
3115        != Some(
3116            crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
3117        )
3118    {
3119        return None;
3120    }
3121    cpp_name_component_nodes(node)?.last().copied()
3122}
3123
3124fn target_guided_missing_direct_temporary_type<'tree>(
3125    function: Node<'tree>,
3126    ctx: &ScanCtx<'_>,
3127) -> Option<Node<'tree>> {
3128    let target = physically_visible_type_target(ctx)?;
3129    let component_nodes = cpp_name_component_nodes(function)?;
3130    let terminal = component_nodes.last().copied()?;
3131    if node_text(terminal, ctx.source) != target.identifier() {
3132        return None;
3133    }
3134    let components = component_nodes
3135        .iter()
3136        .map(|component| node_text(*component, ctx.source).to_string())
3137        .collect::<Vec<_>>();
3138    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
3139    indexed_scope_matches_target_name(
3140        &indexed_scope,
3141        &components,
3142        is_globally_qualified_cpp_name(function),
3143        target,
3144    )
3145    .then_some(terminal)
3146}
3147
3148fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
3149    let Some(function) = call.child_by_field_name("function") else {
3150        return;
3151    };
3152    if !matches!(
3153        function.kind(),
3154        "identifier"
3155            | "type_identifier"
3156            | "template_function"
3157            | "template_type"
3158            | "qualified_identifier"
3159            | "scoped_identifier"
3160            | "scoped_type_identifier"
3161    ) {
3162        return;
3163    }
3164    if !type_reference_components_may_name_target(function, ctx) {
3165        return;
3166    }
3167    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3168        *ctx.raw_match_count += 1;
3169        for scope in scopes {
3170            push_type_hit(scope, ctx);
3171        }
3172        return;
3173    }
3174    let terminal = function_terminal_node(function);
3175    let name = node_text(terminal, ctx.source);
3176    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3177        return;
3178    }
3179    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3180        match resolve_declaring_member_owner(
3181            &ctx.analyzer,
3182            ctx.visibility,
3183            ctx.file,
3184            &enclosing_owner,
3185            name,
3186        ) {
3187            EnclosingMemberOwnerResolution::Owner(owner)
3188                if matches!(
3189                    ctx.visibility
3190                        .visible_member_for_owner_name(ctx.file, &owner, name,),
3191                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3192                ) =>
3193            {
3194                return;
3195            }
3196            EnclosingMemberOwnerResolution::Ambiguous => return,
3197            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3198        }
3199    }
3200
3201    // A type alias used as a direct temporary (`result_type(value)`) is parsed
3202    // as an ordinary identifier call. Callable lookup can be ambiguous when
3203    // parser recovery flattens a namespace or same-spelled aliases are
3204    // visible from sibling distributions. An exact enclosing class owner
3205    // proves the member alias without treating the call as an arbitrary name;
3206    // retain the shadow and declaration-visibility guards above and below.
3207    if ctx
3208        .analyzer
3209        .type_alias_provider()
3210        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3211        && name == ctx.spec.target.identifier()
3212        && physically_visible_type_target(ctx).is_some()
3213        && !local_type_name_shadows(function, ctx)
3214        && type_alias_owner_matches_structured_reference(function, ctx)
3215        && ctx.visibility.external_type_candidate_visible_in_context(
3216            &ctx.analyzer,
3217            ctx.file,
3218            &ctx.spec.target,
3219            function,
3220        )
3221    {
3222        *ctx.raw_match_count += 1;
3223        push_type_hit(terminal, ctx);
3224        return;
3225    }
3226
3227    let call_resolution = resolve_qualified_call_target(
3228        call,
3229        function,
3230        &ctx.analyzer,
3231        ctx.visibility,
3232        &ctx.ordinary_type_imports,
3233        ctx.file,
3234        ctx.source,
3235    );
3236    match call_resolution {
3237        BareCallTargetResolution::Type(unit) => {
3238            if same_visible_symbol(&unit, &ctx.spec.target) {
3239                *ctx.raw_match_count += 1;
3240                push_type_hit(function, ctx);
3241                return;
3242            }
3243        }
3244        BareCallTargetResolution::FreeFunctions(units)
3245            if units.iter().all(|unit| {
3246                unit.fq_name() == ctx.spec.target.fq_name()
3247                    && ctx
3248                        .visibility
3249                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
3250            }) => {}
3251        BareCallTargetResolution::Ambiguous => {
3252            push_unproven_hit(function, ctx);
3253            return;
3254        }
3255        BareCallTargetResolution::FreeFunctions(_)
3256        | BareCallTargetResolution::UnprovenFreeFunctions(_)
3257        | BareCallTargetResolution::CallableShadow => return,
3258        // Generated `.c` includes can leave ordinary callable resolution with
3259        // no active callable even when target-preserving type resolution can
3260        // prove the constructor's class. Let the structured type fallback
3261        // below make that decision.
3262        BareCallTargetResolution::Missing => {}
3263    }
3264    let target_resolution = resolve_type_node_lexically_for_target(
3265        function,
3266        &ctx.analyzer,
3267        ctx.visibility,
3268        &ctx.ordinary_type_imports,
3269        ctx.file,
3270        ctx.source,
3271        &ctx.spec.target,
3272        Some(&ctx.lexical_scope_cache),
3273        ctx.recovered_sentinel_scope(function).as_deref(),
3274    );
3275    match target_resolution {
3276        LexicalTypeResolution::Resolved {
3277            unit, candidates, ..
3278        } if same_visible_symbol(&unit, &ctx.spec.target)
3279            || candidates
3280                .iter()
3281                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3282        {
3283            *ctx.raw_match_count += 1;
3284            push_type_hit(function, ctx);
3285        }
3286        LexicalTypeResolution::Missing => {
3287            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3288                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3289            {
3290                *ctx.raw_match_count += 1;
3291                push_type_hit(hit, ctx);
3292            }
3293        }
3294        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3295    }
3296}
3297
3298pub enum BareCallTargetResolution {
3299    Type(CodeUnit),
3300    FreeFunctions(Vec<CodeUnit>),
3301    UnprovenFreeFunctions(Vec<CodeUnit>),
3302    CallableShadow,
3303    Ambiguous,
3304    Missing,
3305}
3306
3307pub enum BlockUsingCallTargetResolution {
3308    Target(BareCallTargetResolution),
3309    Unindexed(Vec<String>),
3310    Ambiguous,
3311}
3312
3313#[allow(clippy::too_many_arguments)]
3314fn resolve_qualified_call_target(
3315    call: Node<'_>,
3316    function: Node<'_>,
3317    analyzer: &CppGraphSource<'_>,
3318    visibility: &VisibilityIndex,
3319    ordinary_type_imports: &OrdinaryTypeImportCell,
3320    file: &ProjectFile,
3321    source: &str,
3322) -> BareCallTargetResolution {
3323    if matches!(function.kind(), "identifier" | "template_function") {
3324        return resolve_bare_call_target(
3325            call,
3326            function,
3327            analyzer,
3328            visibility,
3329            ordinary_type_imports,
3330            file,
3331            source,
3332        );
3333    }
3334    if !matches!(
3335        function.kind(),
3336        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3337    ) {
3338        return BareCallTargetResolution::Missing;
3339    }
3340    let terminal = function_terminal_node(function);
3341    let name = node_text(terminal, source);
3342    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3343        return BareCallTargetResolution::Missing;
3344    };
3345    components.push(name.to_string());
3346    let qualified_name = components.join("::");
3347    let type_resolution = resolve_type_node_lexically(
3348        function,
3349        analyzer,
3350        visibility,
3351        ordinary_type_imports,
3352        file,
3353        source,
3354    );
3355    let same_name_resolves_to_type = matches!(
3356        &type_resolution,
3357        LexicalTypeResolution::Resolved {
3358            unit,
3359            candidates,
3360            ..
3361        } if cpp_name_for(unit) == qualified_name
3362            || candidates
3363                .iter()
3364                .any(|candidate| cpp_name_for(candidate) == qualified_name)
3365    );
3366    let has_explicit_template_arguments =
3367        cpp_template_reference_arguments(function, source).is_some();
3368    let candidates = visibility
3369        .visible_identifier_candidates(file, name)
3370        .filter(|candidate| {
3371            candidate.is_function()
3372                && type_owner_of(analyzer, candidate).is_none()
3373                && !(same_name_resolves_to_type
3374                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3375                        || has_explicit_template_arguments
3376                            && visibility
3377                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
3378                && cpp_name_for(candidate) == qualified_name
3379                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3380        })
3381        .cloned()
3382        .collect::<Vec<_>>();
3383    if !candidates.is_empty() {
3384        return resolve_callable_candidates(
3385            candidates,
3386            visibility.call_arity_evidence(file, call, source).exact(),
3387            call.start_byte(),
3388            analyzer,
3389            visibility,
3390            file,
3391        );
3392    }
3393    match type_resolution {
3394        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3395        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3396        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3397    }
3398}
3399
3400fn ordinary_binding_lookup_targets(
3401    binding: &OrdinaryTypeImport,
3402    active_bindings: &[&OrdinaryTypeImport],
3403    name: &str,
3404) -> Vec<Vec<String>> {
3405    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3406        return Vec::new();
3407    };
3408    let mut targets = Vec::new();
3409    match binding.target {
3410        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3411        EffectiveUsingTarget::Namespace { .. } => {
3412            let mut stack = vec![qualified.clone()];
3413            let mut visited = HashSet::default();
3414            while let Some(namespace) = stack.pop() {
3415                if !visited.insert(namespace.clone()) {
3416                    continue;
3417                }
3418                let mut target = namespace.clone();
3419                target.push(name.to_string());
3420                targets.push(target);
3421                stack.extend(active_bindings.iter().filter_map(|candidate| {
3422                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3423                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3424                    .then(|| candidate.resolved_target_components.clone())
3425                    .flatten()
3426                }));
3427            }
3428        }
3429    }
3430    targets
3431}
3432
3433fn binding_free_function_candidates(
3434    binding: &OrdinaryTypeImport,
3435    active_bindings: &[&OrdinaryTypeImport],
3436    analyzer: &CppGraphSource<'_>,
3437    visibility: &VisibilityIndex<'_>,
3438    file: &ProjectFile,
3439    name: &str,
3440    reference_byte: usize,
3441) -> Vec<CodeUnit> {
3442    ordinary_binding_lookup_targets(binding, active_bindings, name)
3443        .into_iter()
3444        .flat_map(|target| {
3445            let qualified_name = target.join("::");
3446            visibility
3447                .visible_identifier_candidates(file, name)
3448                .filter(move |candidate| {
3449                    candidate.is_function()
3450                        && type_owner_of(analyzer, candidate).is_none()
3451                        && cpp_name_for(candidate) == qualified_name
3452                        && visibility.declaration_visible_at(
3453                            analyzer,
3454                            file,
3455                            candidate,
3456                            reference_byte,
3457                        )
3458                })
3459                .cloned()
3460        })
3461        .collect()
3462}
3463
3464/// Collapse the candidates unqualified lookup found to one entry per logical
3465/// callable, keeping the first spelling of each.
3466///
3467/// A header declaration and its out-of-line body spell one parameter type
3468/// differently often enough that the persisted signature strings disagree, so
3469/// the string triple alone reports one C++ declaration as an overload set and
3470/// an unproven argument count then turns it into ambiguity.
3471/// `same_logical_callable` answers the string question first and resolves the
3472/// written parameter names only when the strings differ (#2010).
3473fn dedupe_callable_candidates(
3474    candidates: &mut Vec<CodeUnit>,
3475    analyzer: &CppGraphSource<'_>,
3476    visibility: &VisibilityIndex<'_>,
3477) {
3478    let mut deduped = Vec::with_capacity(candidates.len());
3479    for candidate in candidates.drain(..) {
3480        if !deduped
3481            .iter()
3482            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3483        {
3484            deduped.push(candidate);
3485        }
3486    }
3487    *candidates = deduped;
3488}
3489
3490fn resolve_callable_candidates(
3491    candidates: Vec<CodeUnit>,
3492    call_arity: Option<usize>,
3493    reference_byte: usize,
3494    analyzer: &CppGraphSource<'_>,
3495    visibility: &VisibilityIndex<'_>,
3496    file: &ProjectFile,
3497) -> BareCallTargetResolution {
3498    let mut candidates = candidates;
3499    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3500    if candidates.is_empty() {
3501        return BareCallTargetResolution::Missing;
3502    }
3503    let Some(call_arity) = call_arity else {
3504        // An unproven argument count cannot create ambiguity where lookup found
3505        // exactly one name binding: there is nothing to be ambiguous between.
3506        // C has no overloading at all, and a lone C++ candidate is the only
3507        // declaration unqualified lookup reached, so arity cannot pick another
3508        // one (#1811). Keeping it unproven discarded the proven candidate and
3509        // answered `ambiguous` with an empty definition list.
3510        if candidates.len() == 1 {
3511            return BareCallTargetResolution::FreeFunctions(candidates);
3512        }
3513        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3514    };
3515    let applicable = candidates
3516        .into_iter()
3517        .filter(|candidate| {
3518            visibility
3519                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3520                .is_some_and(|arity| arity.accepts(call_arity))
3521        })
3522        .collect::<Vec<_>>();
3523    if applicable.is_empty() {
3524        BareCallTargetResolution::CallableShadow
3525    } else {
3526        BareCallTargetResolution::FreeFunctions(applicable)
3527    }
3528}
3529
3530fn deduplicate_type_import_candidates(
3531    candidates: Vec<(CodeUnit, Vec<String>)>,
3532) -> Vec<(CodeUnit, Vec<String>)> {
3533    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3534    for candidate in candidates {
3535        if !logical
3536            .iter()
3537            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3538        {
3539            logical.push(candidate);
3540        }
3541    }
3542    logical
3543}
3544
3545fn resolve_direct_type_candidates(
3546    candidates: Vec<(CodeUnit, Vec<String>)>,
3547    analyzer: &CppGraphSource<'_>,
3548    visibility: &VisibilityIndex<'_>,
3549    file: &ProjectFile,
3550) -> BareCallTargetResolution {
3551    let logical = deduplicate_type_import_candidates(candidates);
3552    let [(target, components)] = logical.as_slice() else {
3553        return if logical.is_empty() {
3554            BareCallTargetResolution::Missing
3555        } else {
3556            BareCallTargetResolution::Ambiguous
3557        };
3558    };
3559    match visibility
3560        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3561    {
3562        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3563        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3564        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3565    }
3566}
3567
3568/// Resolve a direct using-declaration in the nearest concrete block before
3569/// class-member lookup. A block declaration such as `using std::swap;` adds
3570/// that name to the block scope and hides a same-named member. If the imported
3571/// target is not indexed, retain its structured path as boundary evidence.
3572#[allow(clippy::too_many_arguments)]
3573pub fn resolve_block_using_call_target(
3574    call: Node<'_>,
3575    function: Node<'_>,
3576    analyzer: &CppGraphSource<'_>,
3577    visibility: &VisibilityIndex<'_>,
3578    ordinary_type_imports: &OrdinaryTypeImportCell,
3579    file: &ProjectFile,
3580    source: &str,
3581) -> Option<BlockUsingCallTargetResolution> {
3582    if !matches!(function.kind(), "identifier" | "template_function") {
3583        return None;
3584    }
3585    let name = node_text(function_terminal_node(function), source);
3586    if name.is_empty() {
3587        return None;
3588    }
3589    let bindings = effective_using_bindings_for_name(
3590        visibility,
3591        ordinary_type_imports,
3592        file,
3593        function,
3594        source,
3595        name,
3596    );
3597    let block_bindings = bindings
3598        .iter()
3599        .filter(|binding| {
3600            binding.namespace_scope.is_none()
3601                && binding.block_scope
3602                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3603        })
3604        .collect::<Vec<_>>();
3605    if block_bindings.is_empty() {
3606        return None;
3607    }
3608    let lexical_scope =
3609        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3610            LexicalScopeResolution::Resolved(scope) => scope,
3611            LexicalScopeResolution::Ambiguous => {
3612                return Some(BlockUsingCallTargetResolution::Ambiguous);
3613            }
3614            LexicalScopeResolution::Missing => return None,
3615        };
3616    let reference_guards = preprocessor_guard_environment(function, source);
3617    let active = block_bindings
3618        .into_iter()
3619        .filter(|binding| {
3620            effective_using_binding_active(
3621                binding,
3622                function,
3623                &lexical_scope,
3624                reference_guards.as_ref(),
3625                visibility,
3626                file,
3627            )
3628        })
3629        .collect::<Vec<_>>();
3630    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3631    let at_tier = active
3632        .into_iter()
3633        .filter(|binding| binding.scope_depth == depth)
3634        .collect::<Vec<_>>();
3635    let callable_candidates = at_tier
3636        .iter()
3637        .flat_map(|binding| {
3638            binding_free_function_candidates(
3639                binding,
3640                &[],
3641                analyzer,
3642                visibility,
3643                file,
3644                name,
3645                call.start_byte(),
3646            )
3647        })
3648        .collect::<Vec<_>>();
3649    if !callable_candidates.is_empty() {
3650        return Some(BlockUsingCallTargetResolution::Target(
3651            resolve_callable_candidates(
3652                callable_candidates,
3653                visibility.call_arity_evidence(file, call, source).exact(),
3654                call.start_byte(),
3655                analyzer,
3656                visibility,
3657                file,
3658            ),
3659        ));
3660    }
3661    let type_candidates = at_tier
3662        .iter()
3663        .flat_map(|binding| {
3664            binding_type_candidates(
3665                binding,
3666                &[],
3667                analyzer,
3668                visibility,
3669                file,
3670                name,
3671                None,
3672                call.start_byte(),
3673            )
3674        })
3675        .collect::<Vec<_>>();
3676    if !type_candidates.is_empty() {
3677        return Some(BlockUsingCallTargetResolution::Target(
3678            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3679        ));
3680    }
3681
3682    let mut unindexed = Vec::new();
3683    for binding in at_tier {
3684        let Some(components) = binding.resolved_target_components.as_ref() else {
3685            continue;
3686        };
3687        if !unindexed.contains(components) {
3688            unindexed.push(components.clone());
3689        }
3690    }
3691    match unindexed.as_slice() {
3692        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3693        [] => None,
3694        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3695    }
3696}
3697
3698#[allow(clippy::too_many_arguments)]
3699pub fn resolve_bare_call_target(
3700    call: Node<'_>,
3701    function: Node<'_>,
3702    analyzer: &CppGraphSource<'_>,
3703    visibility: &VisibilityIndex<'_>,
3704    ordinary_type_imports: &OrdinaryTypeImportCell,
3705    file: &ProjectFile,
3706    source: &str,
3707) -> BareCallTargetResolution {
3708    if !matches!(function.kind(), "identifier" | "template_function") {
3709        return BareCallTargetResolution::Missing;
3710    }
3711    let terminal = function_terminal_node(function);
3712    let name = node_text(terminal, source);
3713    if name.is_empty() {
3714        return BareCallTargetResolution::Missing;
3715    }
3716    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3717    let lexical_scope =
3718        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3719            LexicalScopeResolution::Resolved(scope) => scope,
3720            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3721            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3722        };
3723    let type_resolution = resolve_type_node_lexically(
3724        function,
3725        analyzer,
3726        visibility,
3727        ordinary_type_imports,
3728        file,
3729        source,
3730    );
3731    let type_components = match &type_resolution {
3732        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3733        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3734    };
3735    let direct_type_resolution = visibility.resolve_type_components_lexically(
3736        analyzer,
3737        file,
3738        &[name.to_string()],
3739        false,
3740        &lexical_scope,
3741    );
3742    let direct_type_components = match &direct_type_resolution {
3743        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3744        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3745    };
3746    let has_explicit_template_arguments =
3747        cpp_template_reference_arguments(function, source).is_some();
3748    let bindings = effective_using_bindings_for_name(
3749        visibility,
3750        ordinary_type_imports,
3751        file,
3752        function,
3753        source,
3754        name,
3755    );
3756    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3757    // step re-descends from the root (#1927): with no bindings both filters
3758    // below select nothing, so the environment is never consulted.
3759    let function_guards = if bindings.is_empty() {
3760        None
3761    } else {
3762        preprocessor_guard_environment(function, source)
3763    };
3764    let active_bindings = bindings
3765        .iter()
3766        .filter(|binding| {
3767            effective_using_binding_active(
3768                binding,
3769                function,
3770                &lexical_scope,
3771                function_guards.as_ref(),
3772                visibility,
3773                file,
3774            )
3775        })
3776        .collect::<Vec<_>>();
3777    let transitive_bindings = bindings
3778        .iter()
3779        .filter(|binding| {
3780            effective_using_binding_guards_active(
3781                binding,
3782                function.start_byte(),
3783                function_guards.as_ref(),
3784                visibility,
3785                file,
3786            ) && (binding.namespace_scope.is_some()
3787                || (binding.scope_start <= function.start_byte()
3788                    && function.end_byte() <= binding.scope_end))
3789        })
3790        .collect::<Vec<_>>();
3791    let mut concrete_depths = active_bindings
3792        .iter()
3793        .filter(|binding| binding.namespace_scope.is_none())
3794        .map(|binding| binding.scope_depth)
3795        .collect::<Vec<_>>();
3796    concrete_depths.sort_unstable();
3797    concrete_depths.dedup();
3798    for depth in concrete_depths.into_iter().rev() {
3799        let at_tier = active_bindings
3800            .iter()
3801            .copied()
3802            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3803        let direct = at_tier
3804            .clone()
3805            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3806            .flat_map(|binding| {
3807                binding_free_function_candidates(
3808                    binding,
3809                    &transitive_bindings,
3810                    analyzer,
3811                    visibility,
3812                    file,
3813                    name,
3814                    call.start_byte(),
3815                )
3816            })
3817            .collect::<Vec<_>>();
3818        if !direct.is_empty() {
3819            return resolve_callable_candidates(
3820                direct,
3821                call_arity,
3822                call.start_byte(),
3823                analyzer,
3824                visibility,
3825                file,
3826            );
3827        }
3828        let direct_types = at_tier
3829            .clone()
3830            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3831            .flat_map(|binding| {
3832                binding_type_candidates(
3833                    binding,
3834                    &transitive_bindings,
3835                    analyzer,
3836                    visibility,
3837                    file,
3838                    name,
3839                    None,
3840                    call.start_byte(),
3841                )
3842            })
3843            .collect::<Vec<_>>();
3844        if !direct_types.is_empty() {
3845            // `resolve_direct_type_candidates` never consults the argument
3846            // count: it answers the one type the name binds to, or reports the
3847            // competing types. An unknown count therefore cannot make this
3848            // ambiguous (#1812).
3849            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3850        }
3851        let directives = at_tier
3852            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3853            .flat_map(|binding| {
3854                binding_free_function_candidates(
3855                    binding,
3856                    &transitive_bindings,
3857                    analyzer,
3858                    visibility,
3859                    file,
3860                    name,
3861                    call.start_byte(),
3862                )
3863            })
3864            .collect::<Vec<_>>();
3865        if !directives.is_empty() {
3866            return resolve_callable_candidates(
3867                directives,
3868                call_arity,
3869                call.start_byte(),
3870                analyzer,
3871                visibility,
3872                file,
3873            );
3874        }
3875    }
3876    for prefix_len in (0..=lexical_scope.len()).rev() {
3877        let mut qualified = lexical_scope[..prefix_len].to_vec();
3878        qualified.push(name.to_string());
3879        let same_name_resolves_to_type = direct_type_components
3880            .is_some_and(|components| components == qualified.as_slice())
3881            || type_components.is_some_and(|components| components == qualified.as_slice());
3882        let mut direct = visibility
3883            .visible_identifier_candidates(file, name)
3884            .filter(|candidate| {
3885                candidate.is_function()
3886                    && type_owner_of(analyzer, candidate).is_none()
3887                    && !(same_name_resolves_to_type
3888                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3889                            || has_explicit_template_arguments
3890                                && visibility
3891                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3892                    && cpp_name_for(candidate) == qualified.join("::")
3893                    && if analyzer.reference_uses_c_semantics(file) {
3894                        visibility.declaration_visible_for_c_forward_call(
3895                            analyzer,
3896                            file,
3897                            candidate,
3898                            call.start_byte(),
3899                        )
3900                    } else {
3901                        visibility.declaration_visible_at(
3902                            analyzer,
3903                            file,
3904                            candidate,
3905                            call.start_byte(),
3906                        )
3907                    }
3908            })
3909            .cloned()
3910            .collect::<Vec<_>>();
3911        let at_tier = active_bindings.iter().copied().filter(|binding| {
3912            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3913        });
3914        direct.extend(
3915            at_tier
3916                .clone()
3917                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3918                .flat_map(|binding| {
3919                    binding_free_function_candidates(
3920                        binding,
3921                        &transitive_bindings,
3922                        analyzer,
3923                        visibility,
3924                        file,
3925                        name,
3926                        call.start_byte(),
3927                    )
3928                }),
3929        );
3930        if !direct.is_empty() {
3931            return resolve_callable_candidates(
3932                direct,
3933                call_arity,
3934                call.start_byte(),
3935                analyzer,
3936                visibility,
3937                file,
3938            );
3939        }
3940        let mut direct_types = at_tier
3941            .clone()
3942            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3943            .flat_map(|binding| {
3944                binding_type_candidates(
3945                    binding,
3946                    &transitive_bindings,
3947                    analyzer,
3948                    visibility,
3949                    file,
3950                    name,
3951                    None,
3952                    call.start_byte(),
3953                )
3954            })
3955            .collect::<Vec<_>>();
3956        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3957            && let LexicalTypeResolution::Resolved {
3958                unit, components, ..
3959            } = &direct_type_resolution
3960        {
3961            direct_types.push((unit.clone(), components.clone()));
3962        }
3963        if !direct_types.is_empty() {
3964            // `resolve_direct_type_candidates` never consults the argument
3965            // count: it answers the one type the name binds to, or reports the
3966            // competing types. An unknown count therefore cannot make this
3967            // ambiguous (#1812).
3968            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3969        }
3970        let directives = at_tier
3971            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3972            .flat_map(|binding| {
3973                binding_free_function_candidates(
3974                    binding,
3975                    &transitive_bindings,
3976                    analyzer,
3977                    visibility,
3978                    file,
3979                    name,
3980                    call.start_byte(),
3981                )
3982            })
3983            .collect::<Vec<_>>();
3984        if !directives.is_empty() {
3985            return resolve_callable_candidates(
3986                directives,
3987                call_arity,
3988                call.start_byte(),
3989                analyzer,
3990                visibility,
3991                file,
3992            );
3993        }
3994        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3995            // The lexical type resolution below already answers with the single
3996            // type, or with its own ambiguity verdict; the argument count adds
3997            // nothing to that decision (#1812).
3998            return match type_resolution {
3999                LexicalTypeResolution::Resolved { unit, .. } => {
4000                    BareCallTargetResolution::Type(unit)
4001                }
4002                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
4003                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
4004            };
4005        }
4006    }
4007    // Every lookup tier is exhausted: no callable and no type candidate was
4008    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
4009    // at all, and its early return in get_definition preempted the same-file
4010    // macro fallback - so a call to a macro defined in the referencing file
4011    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
4012    // resolve once an unresolvable include made the argument count unknown.
4013    // A no-candidate outcome is Missing, which is what makes the fallback
4014    // reachable (#1812).
4015    match type_resolution {
4016        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
4017        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
4018        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
4019    }
4020}
4021
4022fn static_qualifier_type_scopes<'tree>(
4023    node: Node<'tree>,
4024    ctx: &ScanCtx<'_>,
4025) -> Option<Vec<Node<'tree>>> {
4026    if !matches!(
4027        node.kind(),
4028        "qualified_identifier" | "scoped_type_identifier"
4029    ) {
4030        return None;
4031    }
4032    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
4033    // this root contains every structured component needed for prefix lookup.
4034    debug_assert!(!is_nested_type_node(node));
4035    let qualified = qualified_owner_components(node, ctx.source)?;
4036    static_qualifier_type_scopes_for_components(node, qualified, ctx)
4037}
4038
4039fn static_qualifier_type_scopes_for_components<'tree>(
4040    node: Node<'tree>,
4041    qualified: QualifiedOwnerComponents<'tree>,
4042    ctx: &ScanCtx<'_>,
4043) -> Option<Vec<Node<'tree>>> {
4044    if !qualified.global
4045        && let Some(name) = qualified.names.first()
4046        && let Some(alias_type) = local_receiver_alias_type_node(node, name, ctx)
4047    {
4048        return match resolve_receiver_type_node(alias_type, ctx) {
4049            Ok(Some(owner)) if same_visible_symbol(&owner, &ctx.spec.target) => {
4050                Some(vec![qualified.nodes[0]])
4051            }
4052            _ => None,
4053        };
4054    }
4055    let mut matches = Vec::new();
4056    let mut inherited_injected_name_is_shadowed = false;
4057    for component_count in 1..=qualified.names.len() {
4058        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
4059            node,
4060            &qualified.names[..component_count],
4061            qualified.global,
4062            &ctx.analyzer,
4063            ctx.visibility,
4064            &ctx.ordinary_type_imports,
4065            ctx.file,
4066            ctx.source,
4067            &ctx.spec.target,
4068            false,
4069            Some(&ctx.lexical_scope_cache),
4070        );
4071        match resolution {
4072            LexicalTypeResolution::Resolved {
4073                unit, candidates, ..
4074            } if (!ctx
4075                .analyzer
4076                .type_alias_provider()
4077                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4078                || ctx.visibility.external_type_candidate_visible_in_context(
4079                    &ctx.analyzer,
4080                    ctx.file,
4081                    &ctx.spec.target,
4082                    node,
4083                ))
4084                && (same_visible_symbol(&unit, &ctx.spec.target)
4085                    || candidates
4086                        .iter()
4087                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
4088                && target_alias_candidates_visible(&candidates, node, ctx) =>
4089            {
4090                let matched =
4091                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
4092                if !template_type_component_preserves_target(matched, &candidates, ctx) {
4093                    continue;
4094                }
4095                if !matches.iter().any(|existing: &Node<'_>| {
4096                    existing.start_byte() == matched.start_byte()
4097                        && existing.end_byte() == matched.end_byte()
4098                }) {
4099                    matches.push(matched);
4100                }
4101            }
4102            // The ordinary lexical resolver can remain ambiguous when the
4103            // qualified terminal is an alias whose canonical target is not
4104            // indexed (for example, `Hash::Digest` aliases an external
4105            // `std::array`). The target-guided path below still requires one
4106            // physically visible logical class for every emitted prefix.
4107            LexicalTypeResolution::Ambiguous => {
4108                return (!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            }
4114            LexicalTypeResolution::Resolved { .. } => {
4115                if let Some(matched) =
4116                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4117                {
4118                    matches.push(matched);
4119                }
4120                inherited_injected_name_is_shadowed |= component_count == 1;
4121            }
4122            LexicalTypeResolution::Missing => {
4123                if let Some(matched) =
4124                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4125                {
4126                    matches.push(matched);
4127                }
4128            }
4129        }
4130    }
4131    if matches.is_empty() {
4132        (!inherited_injected_name_is_shadowed)
4133            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4134            .flatten()
4135            .map(|scope| vec![scope])
4136            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
4137    } else {
4138        Some(matches)
4139    }
4140}
4141
4142/// Recover a class owner in a qualified expression when guard-aware lookup
4143/// cannot prove the owner. Keep the hit on the owner component, not the member.
4144enum TargetGuidedQualifiedValueOwnerHit<'tree> {
4145    Proven(Node<'tree>),
4146    Unproven(Node<'tree>),
4147}
4148
4149fn target_guided_qualified_value_owner_hit<'tree>(
4150    node: Node<'tree>,
4151    ctx: &ScanCtx<'_>,
4152) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
4153    let target = physically_visible_type_target(ctx)?;
4154    if !target.is_class() {
4155        return None;
4156    }
4157    let qualified = qualified_owner_components(node, ctx.source)?;
4158    let lexical_scope = match enclosing_lexical_scope_components(
4159        node,
4160        &ctx.analyzer,
4161        ctx.visibility,
4162        ctx.file,
4163        ctx.source,
4164    ) {
4165        LexicalScopeResolution::Resolved(scope) => scope,
4166        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4167            enclosing_namespace_components(node, ctx.source)
4168        }
4169    };
4170    let LexicalTypeResolution::Resolved {
4171        unit, candidates, ..
4172    } = ctx.visibility.resolve_type_components_lexically_for_target(
4173        &ctx.analyzer,
4174        ctx.file,
4175        &qualified.names,
4176        qualified.global,
4177        &lexical_scope,
4178        target,
4179    )
4180    else {
4181        return None;
4182    };
4183    let scope = qualified.nodes.last().copied()?;
4184    // Lexical component lookup does not consider source order, so it can
4185    // select a declaration that follows the reference. The forward selection
4186    // proves the owner only when that declaration is visible at the reference.
4187    let forward_uniquely_selects_target = match ctx
4188        .visibility
4189        .resolve_type_components_lexically_for_forward(
4190            &ctx.analyzer,
4191            ctx.file,
4192            &qualified.names,
4193            qualified.global,
4194            &lexical_scope,
4195        ) {
4196        LexicalTypeResolution::Resolved {
4197            unit, candidates, ..
4198        } if candidates.len() == 1 => {
4199            same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4200                && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4201                && ctx.visibility.external_type_candidate_visible_in_context(
4202                    &ctx.analyzer,
4203                    ctx.file,
4204                    &candidates[0],
4205                    scope,
4206                )
4207        }
4208        LexicalTypeResolution::Resolved { .. }
4209        | LexicalTypeResolution::Ambiguous
4210        | LexicalTypeResolution::Missing => false,
4211    };
4212    if forward_uniquely_selects_target
4213        || (same_visible_symbol(&unit, target)
4214            && !candidates.is_empty()
4215            && candidates
4216                .iter()
4217                .all(|candidate| same_visible_symbol(candidate, target)))
4218    {
4219        Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4220    } else if candidates
4221        .iter()
4222        .any(|candidate| same_visible_symbol(candidate, target))
4223    {
4224        Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4225    } else {
4226        None
4227    }
4228}
4229
4230/// Resolve a nested class-owned alias when the indexed alias path is not a
4231/// standalone type candidate. The C++ index stores `basic_json::type_error`
4232/// as a synthetic child of `basic_json`, while source can qualify it through
4233/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4234/// then canonicalize the structured member alias against the requested type.
4235fn target_guided_nested_alias_type_scope<'tree>(
4236    node: Node<'tree>,
4237    qualified: &QualifiedOwnerComponents<'tree>,
4238    component_count: usize,
4239    ctx: &ScanCtx<'_>,
4240) -> Option<Node<'tree>> {
4241    if component_count < 2 {
4242        return None;
4243    }
4244    let (owner_components, member_name) =
4245        qualified.names[..component_count].split_at(component_count - 1);
4246    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4247        node,
4248        owner_components,
4249        qualified.global,
4250        &ctx.analyzer,
4251        ctx.visibility,
4252        &ctx.ordinary_type_imports,
4253        ctx.file,
4254        ctx.source,
4255    ) else {
4256        return None;
4257    };
4258    let member_name = member_name.first()?;
4259    let alias_provider = ctx.analyzer.type_alias_provider()?;
4260    ctx.visibility
4261        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4262        .into_iter()
4263        .filter(|member| alias_provider.is_type_alias(member))
4264        .find(|member| {
4265            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4266                &ctx.analyzer,
4267                ctx.file,
4268                member,
4269                node,
4270            ) || ctx
4271                .visibility
4272                .external_type_candidate_guard_compatible_in_context(
4273                    &ctx.analyzer,
4274                    ctx.file,
4275                    member,
4276                    node,
4277                );
4278            if !member_visible {
4279                return false;
4280            }
4281            same_visible_symbol(member, &ctx.spec.target)
4282                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4283        })
4284        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4285}
4286
4287fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4288    if ctx.visibility.structured_class_alias_resolves_to_target(
4289        &ctx.analyzer,
4290        ctx.file,
4291        candidate,
4292        &ctx.spec.target,
4293    ) {
4294        return ctx.spec.target.clone();
4295    }
4296    let structured = ctx
4297        .visibility
4298        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4299    structured.unwrap_or_else(|| candidate.clone())
4300}
4301
4302/// Preserve the alias component of a qualified reference when the alias target
4303/// is a dependent nested type and forward lookup retains its primary template
4304/// as the bounded identity.
4305fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4306    ctx.visibility
4307        .external_type_candidate_guard_compatible_in_context(
4308            &ctx.analyzer,
4309            ctx.file,
4310            candidate,
4311            node,
4312        )
4313}
4314
4315fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4316    ctx.visibility.is_physically_visible(ctx.file, candidate)
4317        && class_alias_guard_compatible(candidate, node, ctx)
4318}
4319
4320fn class_alias_path_preserves_target(
4321    candidate: &CodeUnit,
4322    target: &CodeUnit,
4323    ctx: &ScanCtx<'_>,
4324) -> bool {
4325    ctx.visibility.structured_class_alias_path_preserves_target(
4326        &ctx.analyzer,
4327        ctx.file,
4328        candidate,
4329        target,
4330    )
4331}
4332
4333fn target_guided_dependent_alias_qualifier_scope<'tree>(
4334    node: Node<'tree>,
4335    ctx: &ScanCtx<'_>,
4336) -> Option<Node<'tree>> {
4337    if !matches!(
4338        node.kind(),
4339        "qualified_identifier" | "scoped_type_identifier"
4340    ) {
4341        return None;
4342    }
4343    let target = physically_visible_type_target(ctx)?;
4344    let alias_provider = ctx.analyzer.type_alias_provider()?;
4345    if !target.is_class() || alias_provider.is_type_alias(target) {
4346        return None;
4347    }
4348    let nodes = cpp_name_component_nodes(node)?;
4349    let names = nodes
4350        .iter()
4351        .map(|component| node_text(*component, ctx.source).to_string())
4352        .collect::<Vec<_>>();
4353    let global = is_globally_qualified_cpp_name(node);
4354    let lexical_scope = match enclosing_lexical_scope_components(
4355        node,
4356        &ctx.analyzer,
4357        ctx.visibility,
4358        ctx.file,
4359        ctx.source,
4360    ) {
4361        LexicalScopeResolution::Resolved(scope) => scope,
4362        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4363            enclosing_namespace_components(node, ctx.source)
4364        }
4365    };
4366    for component_count in 1..=names.len() {
4367        let components = &names[..component_count];
4368        let name = components.last()?;
4369        let candidates = ctx
4370            .visibility
4371            .visible_identifier_candidates(ctx.file, name)
4372            .filter(|candidate| alias_provider.is_type_alias(candidate))
4373            .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4374            .filter(|candidate| {
4375                let candidate_components = canonical_cpp_scope_components(candidate);
4376                lexical_component_tiers(components, global, &lexical_scope)
4377                    .any(|tier| tier == candidate_components)
4378                    || (component_count == 1
4379                        && member_alias_owner_matches_reference_for(
4380                            candidate,
4381                            nodes[component_count - 1],
4382                            ctx,
4383                        ))
4384            })
4385            .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4386        let mut aliases: Vec<&CodeUnit> = Vec::new();
4387        for candidate in candidates {
4388            if !aliases
4389                .iter()
4390                .any(|existing| same_logical_symbol(existing, candidate))
4391            {
4392                aliases.push(candidate);
4393            }
4394        }
4395        if aliases.len() == 1 {
4396            return nodes.get(component_count - 1).copied();
4397        }
4398        if aliases.len() > 1 {
4399            return None;
4400        }
4401    }
4402    None
4403}
4404
4405/// Preserve an unqualified class-owned alias when its dependent target path
4406/// retains the requested primary template as the bounded forward identity.
4407fn target_guided_dependent_class_alias_leaf<'tree>(
4408    node: Node<'tree>,
4409    ctx: &ScanCtx<'_>,
4410) -> Option<Node<'tree>> {
4411    if node.kind() != "type_identifier"
4412        || is_declaration_name(node)
4413        || local_type_name_shadows(node, ctx)
4414    {
4415        return None;
4416    }
4417    let target = physically_visible_type_target(ctx)?;
4418    let alias_provider = ctx.analyzer.type_alias_provider()?;
4419    if !target.is_class() || alias_provider.is_type_alias(target) {
4420        return None;
4421    }
4422    let name = node_text(node, ctx.source);
4423    let aliases = ctx
4424        .visibility
4425        .visible_identifier_candidates(ctx.file, name)
4426        .filter(|candidate| alias_provider.is_type_alias(candidate))
4427        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4428        .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4429        .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4430        .collect::<Vec<_>>();
4431    matches!(aliases.as_slice(), [_]).then_some(node)
4432}
4433
4434/// Recover a namespace alias whose guard state blocks ordinary visibility.
4435/// Require one visible canonical target and an exact structured alias path.
4436fn target_guided_unproven_alias_type_reference<'tree>(
4437    node: Node<'tree>,
4438    candidates: &[CodeUnit],
4439    ctx: &ScanCtx<'_>,
4440) -> Option<Node<'tree>> {
4441    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4442    let target = physically_visible_type_target(ctx)?;
4443    if !target.is_class() {
4444        return None;
4445    }
4446    let alias_provider = ctx.analyzer.type_alias_provider()?;
4447    let (components, _) = type_reference_components(node, ctx.source)?;
4448    let hit = template_arguments
4449        .as_ref()
4450        .and_then(|_| template_reference_name_node(node))
4451        .map(function_terminal_node)
4452        .unwrap_or_else(|| function_terminal_node(node));
4453    candidates
4454        .iter()
4455        .filter(|candidate| {
4456            alias_provider.is_type_alias(candidate)
4457                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4458                && canonical_cpp_scope_components(candidate) == components
4459        })
4460        .find(|candidate| {
4461            template_arguments.as_ref().map_or_else(
4462                || {
4463                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4464                        || ctx.visibility.structured_alias_primary_preserves_target(
4465                            &ctx.analyzer,
4466                            ctx.file,
4467                            candidate,
4468                            target,
4469                        )
4470                },
4471                |arguments| {
4472                    ctx.visibility.template_alias_arguments_preserve_target(
4473                        &ctx.analyzer,
4474                        ctx.file,
4475                        candidate,
4476                        arguments,
4477                        target,
4478                    )
4479                },
4480            )
4481        })
4482        .map(|_| hit)
4483}
4484
4485fn target_alias_candidates_visible(
4486    candidates: &[CodeUnit],
4487    reference: Node<'_>,
4488    ctx: &ScanCtx<'_>,
4489) -> bool {
4490    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4491        return true;
4492    };
4493    if candidates.iter().any(|candidate| {
4494        !alias_provider.is_type_alias(candidate)
4495            && ctx.visibility.same_template_member_identity(
4496                &ctx.analyzer,
4497                candidate,
4498                &ctx.spec.target,
4499            )
4500    }) {
4501        return true;
4502    }
4503    let target_aliases = candidates
4504        .iter()
4505        .filter(|candidate| {
4506            alias_provider.is_type_alias(candidate)
4507                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4508        })
4509        .collect::<Vec<_>>();
4510    target_aliases.is_empty()
4511        || target_aliases
4512            .iter()
4513            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4514}
4515
4516fn type_candidate_visible_at_reference(
4517    candidate: &CodeUnit,
4518    reference: Node<'_>,
4519    ctx: &ScanCtx<'_>,
4520) -> bool {
4521    let class_owned_alias = ctx
4522        .analyzer
4523        .type_alias_provider()
4524        .is_some_and(|provider| provider.is_type_alias(candidate))
4525        && ctx
4526            .analyzer
4527            .parent_of(candidate)
4528            .is_some_and(|owner| owner.is_class());
4529    if class_owned_alias {
4530        let conditional_family = ctx
4531            .visibility
4532            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4533        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4534            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4535            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4536        let guard_match = ctx
4537            .visibility
4538            .external_type_candidate_guard_compatible_in_context(
4539                &ctx.analyzer,
4540                ctx.file,
4541                candidate,
4542                reference,
4543            );
4544        let general_match = conditional_family
4545            && ctx.visibility.external_type_candidate_visible_in_context(
4546                &ctx.analyzer,
4547                ctx.file,
4548                candidate,
4549                reference,
4550            );
4551        return owner_match && (guard_match || general_match);
4552    }
4553    ctx.visibility.external_type_candidate_visible_in_context(
4554        &ctx.analyzer,
4555        ctx.file,
4556        candidate,
4557        reference,
4558    )
4559}
4560
4561fn unqualified_reference_selects_inherited_alias(
4562    candidate: &CodeUnit,
4563    reference: Node<'_>,
4564    ctx: &ScanCtx<'_>,
4565) -> bool {
4566    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4567        return false;
4568    };
4569    if global || components.len() != 1 {
4570        return false;
4571    }
4572    matches!(
4573        resolve_type_node_lexically_for_target(
4574            reference,
4575            &ctx.analyzer,
4576            ctx.visibility,
4577            &ctx.ordinary_type_imports,
4578            ctx.file,
4579            ctx.source,
4580            candidate,
4581            Some(&ctx.lexical_scope_cache),
4582            ctx.recovered_sentinel_scope(reference).as_deref(),
4583        ),
4584        LexicalTypeResolution::Resolved {
4585            ref unit,
4586            ref candidates,
4587            ..
4588        } if ctx
4589            .visibility
4590            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4591            || candidates.iter().any(|resolved| {
4592                ctx.visibility.same_template_member_identity(
4593                    &ctx.analyzer,
4594                    resolved,
4595                    candidate,
4596                )
4597            })
4598    )
4599}
4600
4601fn qualified_reference_selects_type_candidate(
4602    candidate: &CodeUnit,
4603    reference: Node<'_>,
4604    ctx: &ScanCtx<'_>,
4605) -> bool {
4606    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4607        return false;
4608    };
4609    if components.len() < 2 {
4610        return false;
4611    }
4612    let candidate_components = canonical_cpp_scope_components(candidate);
4613    match select_qualifier_owner(
4614        reference,
4615        global,
4616        &ctx.analyzer,
4617        ctx.visibility,
4618        &ctx.ordinary_type_imports,
4619        ctx.file,
4620        ctx.source,
4621    ) {
4622        QualifierOwnerSelection::Unchanged => {}
4623        QualifierOwnerSelection::Selected(selected) => {
4624            return selected == candidate_components;
4625        }
4626        QualifierOwnerSelection::Ambiguous => return false,
4627    }
4628    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4629        match enclosing_lexical_scope_components(
4630            reference,
4631            &ctx.analyzer,
4632            ctx.visibility,
4633            ctx.file,
4634            ctx.source,
4635        ) {
4636            LexicalScopeResolution::Resolved(scope) => scope,
4637            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4638                enclosing_namespace_components(reference, ctx.source)
4639            }
4640        }
4641    });
4642    lexical_component_tiers(&components, global, &lexical_scope)
4643        .any(|qualified| qualified == candidate_components)
4644}
4645
4646fn qualified_type_component_hit_node<'tree>(
4647    component: Node<'tree>,
4648    qualified: Node<'tree>,
4649) -> Node<'tree> {
4650    let mut current = component;
4651    while let Some(parent) = current.parent() {
4652        let is_type_name = matches!(
4653            parent.kind(),
4654            "template_type"
4655                | "qualified_identifier"
4656                | "scoped_identifier"
4657                | "scoped_type_identifier"
4658        ) && parent
4659            .child_by_field_name("name")
4660            .is_some_and(|name| same_node(name, current));
4661        if !is_type_name {
4662            break;
4663        }
4664        current = parent;
4665        if same_node(parent, qualified) {
4666            break;
4667        }
4668    }
4669    current
4670}
4671
4672fn template_type_component_preserves_target(
4673    node: Node<'_>,
4674    candidates: &[CodeUnit],
4675    ctx: &ScanCtx<'_>,
4676) -> bool {
4677    template_reference_candidates_select_target(
4678        node,
4679        candidates,
4680        &ctx.analyzer,
4681        ctx.visibility,
4682        ctx.file,
4683        ctx.source,
4684        &ctx.spec.target,
4685    )
4686}
4687
4688fn template_reference_candidates_select_target(
4689    node: Node<'_>,
4690    candidates: &[CodeUnit],
4691    analyzer: &CppGraphSource<'_>,
4692    visibility: &VisibilityIndex<'_>,
4693    file: &ProjectFile,
4694    source: &str,
4695    target: &CodeUnit,
4696) -> bool {
4697    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4698        return !visibility.is_template_specialization(target);
4699    };
4700    let direct_template_name =
4701        template_reference_name_node(node).map(|name| node_text(name, source));
4702    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4703        analyzer.type_alias_provider().is_some_and(|provider| {
4704            visibility
4705                .visible_identifier_candidates(file, name)
4706                .filter(|candidate| provider.is_type_alias(candidate))
4707                .any(|candidate| {
4708                    visibility.template_alias_arguments_preserve_target(
4709                        analyzer, file, candidate, &arguments, target,
4710                    )
4711                })
4712        })
4713    });
4714    named_alias_selects_target
4715        || candidates.iter().any(|candidate| {
4716            (same_visible_symbol(candidate, target)
4717                && visibility.is_primary_template(target)
4718                && direct_template_name == Some(candidate.identifier()))
4719                || visibility.template_alias_arguments_preserve_target(
4720                    analyzer, file, candidate, &arguments, target,
4721                )
4722                || visibility
4723                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4724                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4725        })
4726}
4727
4728fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4729    let template = if node.kind() == "template_type" {
4730        node
4731    } else {
4732        node.child_by_field_name("name")
4733            .filter(|name| name.kind() == "template_type")?
4734    };
4735    template.child_by_field_name("name")
4736}
4737
4738fn type_resolution_matches_target(
4739    node: Node<'_>,
4740    unit: &CodeUnit,
4741    candidates: &[CodeUnit],
4742    ctx: &ScanCtx<'_>,
4743) -> bool {
4744    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4745}
4746
4747fn type_resolution_matches_unit_target(
4748    node: Node<'_>,
4749    unit: &CodeUnit,
4750    candidates: &[CodeUnit],
4751    target: &CodeUnit,
4752    ctx: &ScanCtx<'_>,
4753) -> bool {
4754    target_alias_candidates_visible(candidates, node, ctx)
4755        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4756}
4757
4758/// The identity half of the type-resolution match, without the alias
4759/// visibility gate.
4760///
4761/// Use it only on the without-visibility fallback path, which reports an
4762/// unproven hit. An alias spelling does not contain the target identifier, so
4763/// the name-mention fallback can never recover a rejected alias reference: the
4764/// site would disappear instead of degrading to a reviewable hit.
4765fn type_resolution_identifies_unit_target(
4766    node: Node<'_>,
4767    unit: &CodeUnit,
4768    candidates: &[CodeUnit],
4769    target: &CodeUnit,
4770    ctx: &ScanCtx<'_>,
4771) -> bool {
4772    if !template_alias_owner_matches_reference(node, target, ctx) {
4773        return false;
4774    }
4775    if ctx.visibility.is_template_specialization(target)
4776        && cpp_template_reference_arguments(node, ctx.source).is_some()
4777    {
4778        let selected_unit =
4779            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4780                ctx.visibility
4781                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4782                    .ok()
4783            });
4784        return selected_unit
4785            .as_ref()
4786            .is_some_and(|selected| same_visible_symbol(selected, target))
4787            || template_reference_candidates_select_target(
4788                node,
4789                candidates,
4790                &ctx.analyzer,
4791                ctx.visibility,
4792                ctx.file,
4793                ctx.source,
4794                target,
4795            );
4796    }
4797    unit == target
4798        || ctx
4799            .visibility
4800            .same_template_member_identity(&ctx.analyzer, unit, target)
4801        || ctx.visibility.c_tag_declaration_family_matches_target(
4802            &ctx.analyzer,
4803            ctx.file,
4804            &candidates.iter().collect::<Vec<_>>(),
4805            target,
4806        )
4807        || ctx.visibility.structured_class_alias_resolves_to_target(
4808            &ctx.analyzer,
4809            ctx.file,
4810            unit,
4811            target,
4812        )
4813        || candidates.iter().any(|candidate| {
4814            ctx.visibility
4815                .same_template_member_identity(&ctx.analyzer, candidate, target)
4816                || ctx.visibility.structured_class_alias_resolves_to_target(
4817                    &ctx.analyzer,
4818                    ctx.file,
4819                    candidate,
4820                    target,
4821                )
4822        })
4823}
4824
4825/// Keep a member alias attached to the class specialization that declares it.
4826/// A target-guided lexical lookup can otherwise retain the primary alias when
4827/// the source reference is inside a partial specialization with the same
4828/// unqualified alias name. Compare the indexed template identities instead of
4829/// rendered text or suffixes.
4830fn template_alias_owner_matches_reference(
4831    node: Node<'_>,
4832    target: &CodeUnit,
4833    ctx: &ScanCtx<'_>,
4834) -> bool {
4835    if !ctx
4836        .analyzer
4837        .type_alias_provider()
4838        .is_some_and(|provider| provider.is_type_alias(target))
4839    {
4840        return true;
4841    }
4842    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4843        return true;
4844    };
4845    if !target_owner.is_class() {
4846        return true;
4847    }
4848    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4849        return true;
4850    };
4851    if !ctx.visibility.is_template_specialization(&target_owner)
4852        && !ctx.visibility.is_template_specialization(&reference_owner)
4853    {
4854        return true;
4855    }
4856    same_visible_symbol(&target_owner, &reference_owner)
4857}
4858
4859fn inherited_injected_class_qualifier_scope<'tree>(
4860    node: Node<'tree>,
4861    ctx: &ScanCtx<'_>,
4862) -> Option<Node<'tree>> {
4863    let qualified = qualified_owner_components(node, ctx.source)?;
4864    if qualified.global || qualified.names.is_empty() {
4865        return None;
4866    }
4867    let injected_name = &qualified.names[0];
4868    if !ctx.spec.target.is_class()
4869        || ctx.spec.target.identifier() != injected_name
4870        || physically_visible_type_target(ctx).is_none()
4871    {
4872        return None;
4873    }
4874    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4875    let owner = ctx.visibility.inherited_injected_class_owner(
4876        &ctx.analyzer,
4877        ctx.file,
4878        &enclosing_owner,
4879        injected_name,
4880    )?;
4881    same_visible_symbol(&owner, &ctx.spec.target)
4882        .then(|| qualified.nodes.first().copied())
4883        .flatten()
4884}
4885
4886/// Resolve each qualified type component against the inverse target while
4887/// preserving C++ lexical-tier precedence and structured alias identity.
4888fn target_guided_qualifier_type_scopes<'tree>(
4889    node: Node<'tree>,
4890    ctx: &ScanCtx<'_>,
4891) -> Option<Vec<Node<'tree>>> {
4892    if !matches!(
4893        node.kind(),
4894        "qualified_identifier" | "scoped_type_identifier"
4895    ) {
4896        return None;
4897    }
4898    let target = physically_visible_type_target(ctx)?;
4899    let qualified = qualified_owner_components(node, ctx.source)?;
4900    if !qualified.global
4901        && qualified
4902            .names
4903            .first()
4904            .is_some_and(|name| local_receiver_alias_type_node(node, name, ctx).is_some())
4905    {
4906        return static_qualifier_type_scopes_for_components(node, qualified, ctx);
4907    }
4908    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4909    // scope before falling back to suffix recovery.  A short unqualified
4910    // owner can have a same-spelled class in a nested namespace (for example
4911    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4912    // both as possible owners and then fails closed, even though the
4913    // translation unit's lexical scope selects the global class.  Keep the
4914    // suffix path for malformed namespace sentinels, where the parser does
4915    // not expose every indexed scope component.
4916    let lexical_scope = match enclosing_lexical_scope_components(
4917        node,
4918        &ctx.analyzer,
4919        ctx.visibility,
4920        ctx.file,
4921        ctx.source,
4922    ) {
4923        LexicalScopeResolution::Resolved(scope) => scope,
4924        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4925            enclosing_namespace_components(node, ctx.source)
4926        }
4927    };
4928    let indexed_owner_scope =
4929        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4930    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4931    let mut matches = Vec::new();
4932    for component_count in 1..=qualified.names.len() {
4933        let components = &qualified.names[..component_count];
4934        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4935            .collect::<Vec<_>>();
4936        let name = components.last()?;
4937        let mut candidates = Vec::new();
4938        let mut exact_candidates = Vec::new();
4939        for candidate in ctx
4940            .visibility
4941            .visible_identifier_candidates(ctx.file, name)
4942            .filter(|candidate| candidate.is_class())
4943            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4944        {
4945            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4946                brokk_bifrost_core::analyzer::Language::Cpp,
4947                &cpp_name_for(candidate),
4948            );
4949            if !candidate_components.ends_with(components)
4950                || candidates
4951                    .iter()
4952                    .any(|existing| same_logical_symbol(existing, candidate))
4953            {
4954                continue;
4955            }
4956            let exact_lexical_scope = lexical_tiers
4957                .iter()
4958                .any(|expected| expected == &candidate_components);
4959            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4960            let structured_owner_match = indexed_owner_scope
4961                .as_ref()
4962                .is_some_and(|owner| owner.starts_with(candidate_owner))
4963                || recovered_owner_scope
4964                    .as_ref()
4965                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4966            let class_alias_owner_match = ctx
4967                .analyzer
4968                .type_alias_provider()
4969                .is_some_and(|provider| provider.is_type_alias(candidate))
4970                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4971            let macro_namespace_owner_match = is_declaration_name(node)
4972                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4973            if qualified.global && !exact_lexical_scope {
4974                continue;
4975            }
4976            if components.len() == 1
4977                && candidate_components != components
4978                && !exact_lexical_scope
4979                && !structured_owner_match
4980                && !class_alias_owner_match
4981                && !macro_namespace_owner_match
4982            {
4983                continue;
4984            }
4985            candidates.push(candidate.clone());
4986            if exact_lexical_scope {
4987                exact_candidates.push(candidate.clone());
4988            }
4989        }
4990        if !exact_candidates.is_empty() {
4991            candidates = exact_candidates;
4992        }
4993        // A typedef spelling can qualify nested C++ members while forward
4994        // lookup canonicalizes that spelling to its underlying class. Preserve
4995        // the exact alias prefix only when structured alias resolution proves
4996        // that it denotes this inverse target.
4997        let canonical_alias_target_matches = matches!(
4998            candidates.as_slice(),
4999            [candidate]
5000                if ctx
5001                    .analyzer
5002                    .type_alias_provider()
5003                    .is_some_and(|provider| provider.is_type_alias(candidate))
5004                    && type_candidate_visible_at_reference(candidate, node, ctx)
5005                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
5006                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5007                        brokk_bifrost_core::analyzer::Language::Cpp,
5008                        &cpp_name_for(candidate),
5009                    ) == components
5010                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
5011        );
5012        let direct_alias_target = ctx
5013            .analyzer
5014            .type_alias_provider()
5015            .is_some_and(|provider| provider.is_type_alias(target))
5016            && candidates
5017                .iter()
5018                .any(|candidate| same_symbol(candidate, target));
5019        let unique_target = matches!(
5020            candidates.as_slice(),
5021            [candidate] if same_visible_symbol(candidate, target)
5022        );
5023        if direct_alias_target || unique_target || canonical_alias_target_matches {
5024            let matched = if ctx
5025                .analyzer
5026                .type_alias_provider()
5027                .is_some_and(|provider| provider.is_type_alias(target))
5028                && ctx
5029                    .analyzer
5030                    .parent_of(target)
5031                    .is_some_and(|owner| owner.is_class())
5032                && ctx
5033                    .visibility
5034                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
5035                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
5036            {
5037                // The alias declaration owns the terminal component. Keep
5038                // the inverse range narrow so `MathLib::bigint` records the
5039                // `bigint` token, not the complete qualified owner path.
5040                qualified.nodes[component_count - 1]
5041            } else {
5042                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
5043            };
5044            if template_type_component_preserves_target(matched, &candidates, ctx) {
5045                matches.push(matched);
5046            }
5047        }
5048    }
5049    (!matches.is_empty()).then_some(matches)
5050}
5051
5052fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
5053    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5054        return false;
5055    };
5056    ctx.visibility
5057        .visible_identifier_candidates(ctx.file, target.identifier())
5058        .any(|candidate| {
5059            alias_provider.is_type_alias(candidate)
5060                && candidate.identifier() == target.identifier()
5061                && !same_visible_symbol(candidate, target)
5062                && ctx
5063                    .analyzer
5064                    .parent_of(candidate)
5065                    .is_some_and(|owner| owner.is_class())
5066        })
5067}
5068
5069/// Recover an out-of-line owner when the owner declaration and the reference
5070/// use different unknown preprocessor guards. Keep this result unproven.
5071fn target_guided_unproven_out_of_line_owner<'tree>(
5072    node: Node<'tree>,
5073    ctx: &ScanCtx<'_>,
5074) -> Option<Node<'tree>> {
5075    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
5076        || !is_declaration_name(node)
5077    {
5078        return None;
5079    }
5080    let target = physically_visible_type_target(ctx)?;
5081    if !target.is_class() {
5082        return None;
5083    }
5084    let qualified = qualified_owner_components(node, ctx.source)?;
5085    let target_components = canonical_cpp_scope_components(target);
5086    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
5087    let parser_scope = enclosing_namespace_components(node, ctx.source);
5088    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
5089        if !parser_scope.is_empty() || target_namespace.is_empty() {
5090            Some(parser_scope)
5091        } else {
5092            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5093        }
5094    })?;
5095    if has_malformed_wrapper_function_definition_ancestor(node)
5096        && target_namespace.starts_with(&scope)
5097        && target_namespace.len() > scope.len()
5098    {
5099        scope = target_namespace.to_vec();
5100    }
5101    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
5102        .any(|components| components == target_components)
5103    {
5104        return None;
5105    }
5106    let owner_name = qualified.names.last()?;
5107    let candidates = ctx
5108        .visibility
5109        .visible_identifier_candidates(ctx.file, owner_name)
5110        .filter(|candidate| {
5111            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
5112        })
5113        .collect::<Vec<_>>();
5114    if candidates.is_empty()
5115        || candidates
5116            .iter()
5117            .any(|candidate| !same_visible_symbol(candidate, target))
5118    {
5119        return None;
5120    }
5121    qualified.nodes.last().copied()
5122}
5123
5124fn macro_namespace_scope_matches(
5125    candidate_owner: &[String],
5126    node: Node<'_>,
5127    ctx: &ScanCtx<'_>,
5128) -> bool {
5129    let namespace = enclosing_namespace_components(node, ctx.source);
5130    if namespace.is_empty() || candidate_owner.is_empty() {
5131        return false;
5132    }
5133    let mut expanded_owner = Vec::new();
5134    for component in candidate_owner {
5135        if let Some(replacement) =
5136            ctx.visibility
5137                .object_macro_replacement_at(ctx.file, component, node.start_byte())
5138        {
5139            let replacement_components =
5140                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5141                    brokk_bifrost_core::analyzer::Language::Cpp,
5142                    &replacement,
5143                );
5144            if replacement_components.is_empty() {
5145                return false;
5146            }
5147            expanded_owner.extend(replacement_components);
5148        } else {
5149            expanded_owner.push(component.clone());
5150        }
5151    }
5152    expanded_owner == namespace
5153}
5154
5155fn target_guided_missing_type_leaf<'tree>(
5156    node: Node<'tree>,
5157    ctx: &ScanCtx<'_>,
5158) -> Option<Node<'tree>> {
5159    physically_visible_type_target(ctx)?;
5160    target_guided_missing_dependent_nested_type_leaf(node, ctx)
5161        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
5162        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
5163        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
5164        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
5165        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
5166}
5167
5168/// Recover a bare class-owned alias whose structured canonical target is the
5169/// requested type. The class owner must enclose the reference, and every alias
5170/// with that spelling in the owner chain must preserve the same target.
5171fn target_guided_missing_class_alias_target_type_leaf<'tree>(
5172    node: Node<'tree>,
5173    ctx: &ScanCtx<'_>,
5174) -> Option<Node<'tree>> {
5175    if node.kind() != "type_identifier"
5176        || is_declaration_name(node)
5177        || local_type_name_shadows(node, ctx)
5178    {
5179        return None;
5180    }
5181    let alias_provider = ctx.analyzer.type_alias_provider()?;
5182    let name = node_text(node, ctx.source);
5183    let aliases = ctx
5184        .visibility
5185        .visible_identifier_candidates(ctx.file, name)
5186        .filter(|candidate| alias_provider.is_type_alias(candidate))
5187        .filter(|candidate| {
5188            ctx.analyzer
5189                .parent_of(candidate)
5190                .is_some_and(|owner| owner.is_class())
5191        })
5192        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5193        .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5194        .collect::<Vec<_>>();
5195    (!aliases.is_empty()
5196        && aliases.iter().all(|candidate| {
5197            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5198        }))
5199    .then_some(node)
5200}
5201
5202/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5203/// type only when the indexed class owner proves the alias declaration. This
5204/// narrow path covers malformed class bodies without accepting unrelated aliases.
5205fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5206    node: Node<'tree>,
5207    ctx: &ScanCtx<'_>,
5208) -> Option<Node<'tree>> {
5209    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5210        matches!(
5211            declaration.kind(),
5212            "parameter_declaration" | "optional_parameter_declaration"
5213        )
5214    });
5215    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5216        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5217    });
5218    if !parameter && !placement_new_type {
5219        return None;
5220    }
5221    if !ctx
5222        .analyzer
5223        .type_alias_provider()
5224        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5225        || !type_alias_owner_matches_structured_reference(node, ctx)
5226    {
5227        return None;
5228    }
5229    target_guided_missing_declaration_type_leaf(node, ctx)
5230}
5231
5232/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5233/// sentinel prevents ordinary lexical resolution. The indexed target must have
5234/// an indexed class parent, the structured owner path must compose with one
5235/// proven lexical namespace source, and every visible candidate at that exact
5236/// owner path must be the indexed parent. This keeps the fallback owner-based;
5237/// a same-spelled nested type under another template remains unproven.
5238fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5239    node: Node<'tree>,
5240    ctx: &ScanCtx<'_>,
5241) -> Option<Node<'tree>> {
5242    if !matches!(
5243        node.kind(),
5244        "qualified_identifier" | "scoped_type_identifier"
5245    ) || !qualified_type_scope_contains_template(node)
5246    {
5247        return None;
5248    }
5249    let name = node
5250        .child_by_field_name("name")
5251        .filter(|name| name.kind() == "type_identifier")?;
5252    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5253        return None;
5254    }
5255    // Template arguments that select a specialization, or that cannot be
5256    // bound, decide the owner; the owner-spelling fallback below reads the
5257    // qualifier without them and would name the primary template (#6902).
5258    if !matches!(
5259        select_qualifier_owner(
5260            node,
5261            is_globally_qualified_cpp_name(node),
5262            &ctx.analyzer,
5263            ctx.visibility,
5264            &ctx.ordinary_type_imports,
5265            ctx.file,
5266            ctx.source,
5267        ),
5268        QualifierOwnerSelection::Unchanged
5269    ) {
5270        return None;
5271    }
5272    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5273    if !owner_target.is_class() {
5274        return None;
5275    }
5276    let owner = node.child_by_field_name("scope")?;
5277    let owner_resolution = resolve_type_node_lexically_for_target(
5278        owner,
5279        &ctx.analyzer,
5280        ctx.visibility,
5281        &ctx.ordinary_type_imports,
5282        ctx.file,
5283        ctx.source,
5284        &owner_target,
5285        Some(&ctx.lexical_scope_cache),
5286        ctx.recovered_sentinel_scope(owner).as_deref(),
5287    );
5288    if matches!(
5289        owner_resolution,
5290        LexicalTypeResolution::Resolved {
5291            ref unit,
5292            ref candidates,
5293            ..
5294        } if type_resolution_matches_unit_target(
5295            owner,
5296            unit,
5297            candidates,
5298            &owner_target,
5299            ctx,
5300        )
5301    ) {
5302        return Some(name);
5303    }
5304
5305    let qualified = qualified_owner_components(node, ctx.source)?;
5306    let namespace = ctx
5307        .lexical_scope_cache
5308        .orphaned
5309        .enclosing_namespace_components(node, ctx.source);
5310    let indexed_scope = ctx
5311        .recovered_sentinel_scope(node)
5312        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5313        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5314    let owner_components = canonical_cpp_scope_components(&owner_target);
5315    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5316        .any(|components| components == owner_components)
5317    {
5318        return None;
5319    }
5320    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5321        .into_iter()
5322        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5323        .collect::<Vec<_>>();
5324    (!scoped_candidates.is_empty()
5325        && scoped_candidates
5326            .iter()
5327            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5328    .then_some(name)
5329}
5330
5331/// Recover a nested type-alias reference when parser recovery leaves an
5332/// unqualified template argument under a member function.  The ordinary
5333/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5334/// even though the indexed callable owner proves that the reference is inside
5335/// the class which declares the target alias.
5336fn target_guided_missing_member_alias_type_leaf<'tree>(
5337    node: Node<'tree>,
5338    ctx: &ScanCtx<'_>,
5339) -> Option<Node<'tree>> {
5340    if !is_cpp_template_argument_type_leaf(node)
5341        || is_declaration_name(node)
5342        || ctx
5343            .target_declaration_ranges
5344            .iter()
5345            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5346        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5347        || local_type_name_shadows(node, ctx)
5348        || !ctx
5349            .analyzer
5350            .type_alias_provider()
5351            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5352    {
5353        return None;
5354    }
5355    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5356    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5357    if !owner_scope_matches
5358        && !indexed_scope.is_some_and(|scope| {
5359            indexed_scope_matches_target_name(
5360                &scope,
5361                &[ctx.spec.target.identifier().to_string()],
5362                false,
5363                &ctx.spec.target,
5364            )
5365        })
5366    {
5367        return None;
5368    }
5369    // The indexed symbol table intentionally retains declarations from every
5370    // preprocessor branch and from later source positions.  The recovered
5371    // class-owner scope proves the spelling, but it does not prove that this
5372    // alias was active and introduced before the reference.  Apply the same
5373    // structured guard/source-order check used by the ordinary resolver before
5374    // turning the target-guided recovery into a proven hit.
5375    if !(ctx.visibility.external_type_candidate_visible_in_context(
5376        &ctx.analyzer,
5377        ctx.file,
5378        &ctx.spec.target,
5379        node,
5380    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5381    {
5382        return None;
5383    }
5384    let target_visible = ctx
5385        .visibility
5386        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5387        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5388    target_visible.then_some(node)
5389}
5390
5391/// Recover a class/enum template argument only when the parser's ordinary
5392/// lexical lookup failed but the indexed scope and visible declaration still
5393/// prove the exact target. This intentionally excludes aliases: an alias
5394/// argument needs its own template-argument selection path, while a direct
5395/// class/enum argument can be identified by its canonical scope and symbol.
5396fn target_guided_missing_template_argument_type_leaf<'tree>(
5397    node: Node<'tree>,
5398    ctx: &ScanCtx<'_>,
5399) -> Option<Node<'tree>> {
5400    let target = &ctx.spec.target;
5401    let name = node_text(node, ctx.source);
5402    if !target.is_class()
5403        || !is_cpp_template_argument_type_leaf(node)
5404        || is_declaration_name(node)
5405        || name != target.identifier()
5406        || ctx.local_shadows.is_shadowed(name)
5407        || local_type_name_shadows(node, ctx)
5408        || !ctx.visibility.is_physically_visible(ctx.file, target)
5409        || ctx
5410            .analyzer
5411            .type_alias_provider()
5412            .is_some_and(|provider| provider.is_type_alias(target))
5413    {
5414        return None;
5415    }
5416
5417    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5418    let target_components = canonical_cpp_scope_components(target);
5419    if target_components.last().map(String::as_str) != Some(name)
5420        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5421            .any(|components| components == target_components)
5422    {
5423        return None;
5424    }
5425
5426    // The direct visible class candidate supplies the declaration identity;
5427    // the scope check above supplies its canonical owner path. Do not let an
5428    // alias or a same-scoped competing class enter this recovery path.
5429    let candidates = visible_type_identifier_candidates(ctx, name);
5430    if candidates.is_empty()
5431        || candidates.iter().any(|candidate| {
5432            !candidate.is_class()
5433                || ctx
5434                    .analyzer
5435                    .type_alias_provider()
5436                    .is_some_and(|provider| provider.is_type_alias(candidate))
5437                || (!same_visible_symbol(candidate, target)
5438                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5439                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5440        })
5441        || !candidates
5442            .iter()
5443            .any(|candidate| same_visible_symbol(candidate, target))
5444    {
5445        return None;
5446    }
5447
5448    // Physical visibility covers the file/import projection; this second
5449    // guard preserves declaration ordering and preprocessor branch identity.
5450    ctx.visibility
5451        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5452        .then_some(node)
5453}
5454
5455/// Recover the class owner of an out-of-line member whose trailing attribute
5456/// macro was parsed as a separate function definition around the real body.
5457fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5458    let mut function = node;
5459    while function.kind() != "function_definition" {
5460        function = ctx.ancestry.parent(function)?;
5461    }
5462    let macro_name = function_definition_name_node(function)?;
5463    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5464        return None;
5465    }
5466
5467    // An unknown trailing attribute macro can split one real definition into
5468    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5469    // macro-named function definition that owns the body. Recover only that
5470    // exact CST sequence; a complete declaration or a non-macro function is
5471    // an ordinary independent construct.
5472    let declaration = function.prev_named_sibling()?;
5473    if declaration.kind() != "declaration"
5474        || !declaration.has_error()
5475        || function.start_position().row > declaration.end_position().row + 1
5476    {
5477        return None;
5478    }
5479    let mut missing_semicolon = false;
5480    let mut real_semicolon = false;
5481    for child in children_iter(declaration) {
5482        if child.kind() == ";" {
5483            missing_semicolon |= child.is_missing();
5484            real_semicolon |= !child.is_missing();
5485        }
5486    }
5487    if !missing_semicolon || real_semicolon {
5488        return None;
5489    }
5490
5491    let initializer = declaration.child_by_field_name("declarator")?;
5492    if initializer.kind() != "init_declarator"
5493        || initializer
5494            .child_by_field_name("value")
5495            .is_none_or(|value| value.kind() != "argument_list")
5496    {
5497        return None;
5498    }
5499    let qualified_name = initializer
5500        .child_by_field_name("declarator")
5501        .and_then(declarator_name_node)?;
5502    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5503    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5504    match ctx.visibility.resolve_type_components_lexically(
5505        &ctx.analyzer,
5506        ctx.file,
5507        &qualified.names,
5508        qualified.global,
5509        &lexical_scope,
5510    ) {
5511        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5512        LexicalTypeResolution::Resolved { .. }
5513        | LexicalTypeResolution::Ambiguous
5514        | LexicalTypeResolution::Missing => None,
5515    }
5516}
5517
5518/// A class member alias is visible throughout its complete class scope, even
5519/// when its declaration byte follows a recovered out-of-line member's
5520/// trailing return type. Match the indexed owner path structurally before
5521/// allowing the guard-only visibility check above to waive source ordering.
5522fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5523    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5524}
5525
5526fn member_alias_owner_matches_reference_for(
5527    target: &CodeUnit,
5528    node: Node<'_>,
5529    ctx: &ScanCtx<'_>,
5530) -> bool {
5531    let Some(owner) = ctx.analyzer.parent_of(target) else {
5532        return false;
5533    };
5534    if !owner.is_class() {
5535        return false;
5536    }
5537    let reference_owner = ctx
5538        .class_ranges
5539        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5540        .or_else(|| structured_enclosing_owner(node, ctx));
5541    if reference_owner.as_ref().is_some_and(|reference_owner| {
5542        ctx.visibility
5543            .same_template_owner_identity(&owner, reference_owner)
5544    }) {
5545        return true;
5546    }
5547    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5548        ctx.visibility
5549            .same_template_owner_identity(&owner, &reference_owner)
5550    }) {
5551        return true;
5552    }
5553    if reference_owner.is_some_and(|reference_owner| {
5554        matches!(
5555            resolve_declaring_member_owner(
5556                &ctx.analyzer,
5557                ctx.visibility,
5558                ctx.file,
5559                &reference_owner,
5560                target.identifier(),
5561            ),
5562            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5563                if ctx
5564                    .visibility
5565                    .same_template_owner_identity(&owner, &declaring_owner)
5566        )
5567    }) {
5568        return true;
5569    }
5570    let range = Range {
5571        start_byte: node.start_byte(),
5572        end_byte: node.end_byte(),
5573        start_line: node.start_position().row + 1,
5574        end_line: node.end_position().row + 1,
5575    };
5576    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5577    while let Some(candidate) = indexed_enclosing {
5578        if candidate.is_class()
5579            && ctx
5580                .visibility
5581                .same_template_owner_identity(&owner, &candidate)
5582        {
5583            return true;
5584        }
5585        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5586    }
5587    if let Some(reference_body) = malformed_recovered_class_body(node) {
5588        let mut root = node;
5589        while let Some(parent) = ctx.ancestry.parent(root) {
5590            root = parent;
5591        }
5592        if ctx.analyzer.ranges(target).iter().any(|range| {
5593            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5594                .and_then(malformed_recovered_class_body)
5595                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5596        }) {
5597            return true;
5598        }
5599    }
5600    if structured_enclosing_owner(node, ctx)
5601        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5602    {
5603        return true;
5604    }
5605    let owner_components = canonical_cpp_scope_components(&owner);
5606    if ctx
5607        .recovered_sentinel_scope(node)
5608        .is_some_and(|scope| scope == owner_components)
5609    {
5610        return true;
5611    }
5612    if matches!(
5613        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5614            node,
5615            &ctx.analyzer,
5616            ctx.visibility,
5617            ctx.file,
5618            ctx.source,
5619            false,
5620            false,
5621            Some(&ctx.lexical_scope_cache),
5622        ),
5623        LexicalScopeResolution::Resolved(reference_scope)
5624            if reference_scope == owner_components
5625    ) {
5626        return true;
5627    }
5628    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5629    else {
5630        return false;
5631    };
5632    !owner_components.is_empty() && reference_scope == owner_components
5633}
5634
5635fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5636    loop {
5637        if node.kind() == "compound_statement"
5638            && node
5639                .parent()
5640                .is_some_and(|parent| parent.kind() == "declaration_list")
5641            && node.prev_named_sibling().is_some_and(|header| {
5642                header.kind() == "ERROR"
5643                    && header.end_byte() <= node.start_byte()
5644                    && error_contains_class_header(header)
5645            })
5646        {
5647            return Some(node);
5648        }
5649        node = node.parent()?;
5650    }
5651}
5652
5653/// Whether a shallow parser error contains a recognizable class header.
5654pub fn error_contains_class_header(node: Node<'_>) -> bool {
5655    let mut pending = vec![(node, 0usize)];
5656    while let Some((current, depth)) = pending.pop() {
5657        if matches!(current.kind(), "class" | "struct" | "union") {
5658            let mut sibling = current.next_sibling();
5659            let mut saw_name = false;
5660            while let Some(candidate) = sibling {
5661                match candidate.kind() {
5662                    "comment" => {}
5663                    "{" | "base_class_clause" | ":" => return saw_name,
5664                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5665                    _ if !candidate.is_named() => {}
5666                    _ => break,
5667                }
5668                sibling = candidate.next_sibling();
5669            }
5670        }
5671        if depth >= 1 {
5672            continue;
5673        }
5674        let mut cursor = current.walk();
5675        pending.extend(
5676            current
5677                .children(&mut cursor)
5678                .filter(|child| child.kind() != "compound_statement")
5679                .map(|child| (child, depth + 1)),
5680        );
5681    }
5682    false
5683}
5684
5685fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5686    has_ancestor_kind(node, "compound_statement")
5687        && ctx
5688            .visibility
5689            .external_type_candidate_guard_compatible_in_context(
5690                &ctx.analyzer,
5691                ctx.file,
5692                &ctx.spec.target,
5693                node,
5694            )
5695}
5696
5697fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5698    ctx.analyzer
5699        .type_alias_provider()
5700        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5701        && member_alias_owner_matches_reference(node, ctx)
5702}
5703
5704/// A nested class can use aliases declared by any enclosing class. Preserve
5705/// that structured owner chain for malformed macro-return nodes, whose phantom
5706/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5707fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5708    if !ctx
5709        .analyzer
5710        .type_alias_provider()
5711        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5712    {
5713        return false;
5714    }
5715    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5716        return false;
5717    };
5718    let mut reference_owner = structured_enclosing_owner(node, ctx);
5719    while let Some(owner) = reference_owner {
5720        if same_logical_symbol(&target_owner, &owner) {
5721            return true;
5722        }
5723        reference_owner = ctx.analyzer.parent_of(&owner);
5724    }
5725    false
5726}
5727
5728/// An enclosing class alias is only usable when no nearer class declares the
5729/// same type name. The recovered macro-return path does not have a complete
5730/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5731/// rule before the enclosing alias fast path runs.
5732fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5733    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5734        return false;
5735    };
5736    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5737        return false;
5738    };
5739    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5740        return false;
5741    };
5742    let candidates = ctx
5743        .visibility
5744        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5745        .filter(|candidate| {
5746            candidate.is_class()
5747                && alias_provider.is_type_alias(candidate)
5748                && !same_visible_symbol(candidate, &ctx.spec.target)
5749        })
5750        .cloned()
5751        .collect::<Vec<_>>();
5752
5753    let mut owner = Some(reference_owner);
5754    while let Some(owner_unit) = owner {
5755        if same_logical_symbol(&target_owner, &owner_unit) {
5756            return false;
5757        }
5758        if candidates.iter().any(|candidate| {
5759            ctx.analyzer
5760                .parent_of(candidate)
5761                .is_some_and(|candidate_owner| {
5762                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5763                })
5764                && ctx
5765                    .visibility
5766                    .external_type_candidate_guard_compatible_in_context(
5767                        &ctx.analyzer,
5768                        ctx.file,
5769                        candidate,
5770                        node,
5771                    )
5772        }) {
5773            return true;
5774        }
5775        owner = ctx.analyzer.parent_of(&owner_unit);
5776    }
5777    false
5778}
5779
5780/// Whether a type spelling can name a function-local class target at all.
5781///
5782/// Only block-scope lookup of an unqualified head reaches a local class, so
5783/// the spelling must not be `::`-rooted and its head must resolve to the
5784/// target or to a local class that encloses it. Anything else names some
5785/// other type, and neither a proven nor an unproven hit may claim it. Every
5786/// other target is unaffected.
5787fn local_class_target_reachable(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5788    if !ctx.spec.target.is_class() || !is_function_local_unit(&ctx.spec.target) {
5789        return true;
5790    }
5791    type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
5792        !global
5793            && matches!(
5794                function_local_type_resolution(
5795                    &ctx.analyzer,
5796                    ctx.file,
5797                    node,
5798                    &components[..1],
5799                    ctx.source,
5800                ),
5801                Some(LexicalTypeResolution::Resolved { unit, .. })
5802                    if ctx.spec.target.fq().starts_with(unit.fq())
5803            )
5804    })
5805}
5806
5807fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5808    if cpp_active_template_type_parameter(
5809        node,
5810        ctx.spec.target.identifier(),
5811        ctx.source,
5812        &ctx.ancestry,
5813    ) {
5814        return true;
5815    }
5816    // A function-local class target is itself one of the local declarations
5817    // the walk below treats as shadows. A spelling reaches a local class only
5818    // through block-scope lookup of its head: a `::`-rooted name never does,
5819    // and an unqualified name or a qualifier head is shadowed exactly when
5820    // that lookup names another declaration. A later component of a qualified
5821    // name is decided by ordinary resolution, which applies the same lookup
5822    // to its head.
5823    if ctx.spec.target.is_class() && is_function_local_unit(&ctx.spec.target) {
5824        let qualifier = ctx.ancestry.parent(node).filter(|parent| {
5825            matches!(
5826                parent.kind(),
5827                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5828            )
5829        });
5830        if let Some(qualifier) = qualifier {
5831            let scope = qualifier.child_by_field_name("scope");
5832            if scope.is_none() {
5833                return true;
5834            }
5835            let is_head = scope == Some(node)
5836                && ctx.ancestry.parent(qualifier).is_none_or(|outer| {
5837                    !matches!(
5838                        outer.kind(),
5839                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5840                    ) || outer.child_by_field_name("scope") == Some(qualifier)
5841                });
5842            if !is_head {
5843                return false;
5844            }
5845        }
5846        return !matches!(
5847            function_local_type_resolution(
5848                &ctx.analyzer,
5849                ctx.file,
5850                node,
5851                &[ctx.spec.target.identifier().to_string()],
5852                ctx.source,
5853            ),
5854            Some(LexicalTypeResolution::Resolved { unit, .. })
5855                if same_visible_symbol(&unit, &ctx.spec.target)
5856        );
5857    }
5858    let Some(callable) = nearest_callable_scope(node) else {
5859        return false;
5860    };
5861    let mut root_callable = callable;
5862    let mut ancestor = ctx.ancestry.parent(callable);
5863    while let Some(current) = ancestor {
5864        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5865            root_callable = current;
5866        }
5867        ancestor = ctx.ancestry.parent(current);
5868    }
5869
5870    let mut stack = vec![root_callable];
5871    while let Some(current) = stack.pop() {
5872        if current.start_byte() >= node.start_byte() {
5873            continue;
5874        }
5875        if let Some(name) = local_type_name_declaration_node(current)
5876            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5877            && nearest_callable_scope(current).is_some_and(|owner| {
5878                !is_malformed_wrapper_function_definition(owner)
5879                    && owner.start_byte() <= callable.start_byte()
5880                    && callable.end_byte() <= owner.end_byte()
5881            })
5882            && local_alias_scope_contains_node(current, node)
5883        {
5884            return true;
5885        }
5886        let mut cursor = current.walk();
5887        stack.extend(current.named_children(&mut cursor));
5888    }
5889    false
5890}
5891
5892fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5893    local_type_alias_name_node(node).or_else(|| match node.kind() {
5894        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5895            .child_by_field_name("name")
5896            .filter(|name| is_declaration_name(*name)),
5897        _ => None,
5898    })
5899}
5900
5901fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5902    loop {
5903        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5904            return Some(node);
5905        }
5906        node = node.parent()?;
5907    }
5908}
5909
5910fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5911    match node.kind() {
5912        "alias_declaration" => node.child_by_field_name("name"),
5913        "type_definition" => node
5914            .child_by_field_name("declarator")
5915            .and_then(declarator_name_node),
5916        _ => None,
5917    }
5918}
5919
5920fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5921    let mut current = alias.parent();
5922    while let Some(parent) = current {
5923        if matches!(
5924            parent.kind(),
5925            "class_specifier" | "struct_specifier" | "union_specifier"
5926        ) {
5927            return false;
5928        }
5929        if parent.kind() == "compound_statement" {
5930            return parent.start_byte() <= node.start_byte()
5931                && node.end_byte() <= parent.end_byte();
5932        }
5933        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5934            let Some(body) = parent.child_by_field_name("body") else {
5935                return false;
5936            };
5937            return node_is_within(body, alias) && node_is_within(body, node);
5938        }
5939        current = parent.parent();
5940    }
5941    false
5942}
5943
5944fn target_guided_missing_declaration_type_leaf<'tree>(
5945    node: Node<'tree>,
5946    ctx: &ScanCtx<'_>,
5947) -> Option<Node<'tree>> {
5948    if is_declaration_name(node) {
5949        return None;
5950    }
5951    let component_nodes = cpp_name_component_nodes(node)?;
5952    let name_node = component_nodes.last().copied()?;
5953    let name = node_text(name_node, ctx.source);
5954    if name != ctx.spec.target.identifier() {
5955        return None;
5956    }
5957    let inside_target_declaration = ctx
5958        .target_declaration_ranges
5959        .iter()
5960        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5961    if !inside_target_declaration
5962        && !ctx.visibility.external_type_candidate_visible_in_context(
5963            &ctx.analyzer,
5964            ctx.file,
5965            &ctx.spec.target,
5966            node,
5967        )
5968    {
5969        return None;
5970    }
5971    let components = component_nodes
5972        .iter()
5973        .map(|component| node_text(*component, ctx.source).to_string())
5974        .collect::<Vec<_>>();
5975    let local_alias_shadow = local_type_name_shadows(node, ctx);
5976    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5977    let indexed_alias_owner = ctx
5978        .analyzer
5979        .type_alias_provider()
5980        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5981        && member_alias_owner_matches_reference(node, ctx);
5982    let target_alias_self_reference = inside_target_declaration
5983        && ctx
5984            .analyzer
5985            .type_alias_provider()
5986            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5987    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5988        &ctx.analyzer,
5989        ctx.file,
5990        &ctx.spec.target,
5991        node,
5992    ) || member_alias_complete_class_context(node, ctx);
5993    if !target_alias_self_reference
5994        && !local_alias_shadow
5995        && member_alias_visible
5996        && (structured_alias_owner || indexed_alias_owner)
5997    {
5998        return Some(node);
5999    }
6000    let declaration = nearest_declaration_type_context(node)?;
6001    let candidates = visible_type_identifier_candidates(ctx, name);
6002    let unique_visible_target = !candidates.is_empty()
6003        && candidates
6004            .iter()
6005            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
6006    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
6007    let exact_scope_match = indexed_scope_matches_target_name(
6008        &indexed_scope,
6009        &components,
6010        is_globally_qualified_cpp_name(node),
6011        &ctx.spec.target,
6012    );
6013    if matches!(declaration.kind(), "field_declaration" | "declaration") {
6014        let parser_lost_declaration_scope =
6015            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
6016                && unique_visible_target;
6017        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
6018    }
6019    let lost_namespace_parameter_context =
6020        matches!(
6021            declaration.kind(),
6022            "parameter_declaration" | "optional_parameter_declaration"
6023        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
6024    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
6025        return Some(node);
6026    }
6027    None
6028}
6029
6030fn target_guided_missing_alias_rhs_type_leaf<'tree>(
6031    node: Node<'tree>,
6032    ctx: &ScanCtx<'_>,
6033) -> Option<Node<'tree>> {
6034    let mut stack = vec![node];
6035    while let Some(candidate) = stack.pop() {
6036        if candidate.kind() == "type_identifier"
6037            && !is_declaration_name(candidate)
6038            && matches!(
6039                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
6040                Some("template_type")
6041            )
6042        {
6043            let mut current = ctx.ancestry.parent(candidate);
6044            let mut saw_qualified = false;
6045            let mut saw_dependent = false;
6046            let mut saw_type_descriptor = false;
6047            let mut saw_alias_declaration = false;
6048            while let Some(ancestor) = current {
6049                match ancestor.kind() {
6050                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
6051                    "dependent_type" => saw_dependent = true,
6052                    "type_descriptor" => saw_type_descriptor = true,
6053                    "alias_declaration" => {
6054                        saw_alias_declaration = true;
6055                        break;
6056                    }
6057                    "template_type"
6058                    | "template_argument_list"
6059                    | "typename"
6060                    | "template_declaration" => {}
6061                    _ => {}
6062                }
6063                current = ctx.ancestry.parent(ancestor);
6064            }
6065            let name = node_text(candidate, ctx.source);
6066            let visible_candidates = visible_type_identifier_candidates(ctx, name);
6067            let canonical_alias_target = visible_candidates
6068                .iter()
6069                .filter_map(|alias| ctx.visibility.alias_target(alias))
6070                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
6071            let alias_resolves =
6072                ctx.visibility
6073                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
6074                    || canonical_alias_target;
6075            if saw_qualified
6076                && saw_dependent
6077                && saw_type_descriptor
6078                && saw_alias_declaration
6079                && alias_resolves
6080                && ctx.visibility.external_type_candidate_visible_in_context(
6081                    &ctx.analyzer,
6082                    ctx.file,
6083                    &ctx.spec.target,
6084                    candidate,
6085                )
6086            {
6087                return Some(candidate);
6088            }
6089        }
6090        push_named_children_reversed(candidate, &mut stack);
6091    }
6092    None
6093}
6094
6095fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
6096    let mut current = Some(node);
6097    while let Some(ancestor) = current {
6098        if matches!(
6099            ancestor.kind(),
6100            "field_declaration"
6101                | "parameter_declaration"
6102                | "optional_parameter_declaration"
6103                | "declaration"
6104                | "type_descriptor"
6105        ) {
6106            let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
6107            if contains_type
6108                && !(ancestor.kind() == "type_descriptor"
6109                    && is_cpp_template_argument_type_leaf(node))
6110            {
6111                return Some(ancestor);
6112            }
6113            if ancestor.kind() == "type_descriptor"
6114                && ancestor.parent().is_some_and(|parent| {
6115                    matches!(
6116                        parent.kind(),
6117                        "cast_expression"
6118                            | "new_expression"
6119                            | "sizeof_expression"
6120                            | "alignof_expression"
6121                            | "typeid_expression"
6122                    )
6123                })
6124            {
6125                return Some(ancestor);
6126            }
6127        }
6128        if matches!(
6129            ancestor.kind(),
6130            "compound_statement"
6131                | "translation_unit"
6132                | "namespace_definition"
6133                | "alias_declaration"
6134                | "type_definition"
6135                | "base_class_clause"
6136        ) {
6137            return None;
6138        }
6139        current = ancestor.parent();
6140    }
6141    None
6142}
6143
6144fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
6145    let mut candidates = Vec::new();
6146    for candidate in ctx
6147        .visibility
6148        .visible_identifier_candidates(ctx.file, name)
6149        .filter(|candidate| {
6150            candidate.is_class()
6151                || ctx
6152                    .analyzer
6153                    .type_alias_provider()
6154                    .is_some_and(|provider| provider.is_type_alias(candidate))
6155        })
6156    {
6157        if !candidates
6158            .iter()
6159            .any(|existing| same_logical_symbol(existing, candidate))
6160        {
6161            candidates.push(candidate.clone());
6162        }
6163    }
6164    candidates
6165}
6166
6167/// Recover a direct type-alias argument of `static_cast` when parser recovery
6168/// misclassifies a namespace alias as a local declaration. The indexed scope
6169/// and exact alias identity are required so a same-spelled alias in another
6170/// namespace remains excluded.
6171fn target_guided_static_cast_alias_type_descriptor<'tree>(
6172    node: Node<'tree>,
6173    ctx: &ScanCtx<'_>,
6174) -> Option<Node<'tree>> {
6175    if node.kind() != "type_descriptor" {
6176        return None;
6177    }
6178    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
6179        parent.kind() == "template_argument_list"
6180            && parent.named_child_count() == 1
6181            && parent.named_child(0) == Some(node)
6182    })?;
6183    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
6184        parent.kind() == "template_function"
6185            && parent.child_by_field_name("arguments") == Some(argument_list)
6186    })?;
6187    let name = template.child_by_field_name("name")?;
6188    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
6189        return None;
6190    }
6191    let target = &ctx.spec.target;
6192    if node_text(node, ctx.source) != target.identifier()
6193        || !ctx
6194            .analyzer
6195            .type_alias_provider()
6196            .is_some_and(|provider| provider.is_type_alias(target))
6197        || !ctx.visibility.is_physically_visible(ctx.file, target)
6198        || !ctx.visibility.external_type_candidate_visible_in_context(
6199            &ctx.analyzer,
6200            ctx.file,
6201            target,
6202            node,
6203        )
6204    {
6205        return None;
6206    }
6207
6208    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
6209    let target_scope = canonical_cpp_scope_components(target);
6210    let name_components = [target.identifier().to_string()];
6211    if !lexical_component_tiers(&name_components, false, &indexed_scope)
6212        .any(|components| components == target_scope)
6213    {
6214        return None;
6215    }
6216
6217    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
6218    if !candidates
6219        .iter()
6220        .any(|candidate| same_visible_symbol(candidate, target))
6221    {
6222        return None;
6223    }
6224    if candidates.iter().any(|candidate| {
6225        !same_visible_symbol(candidate, target)
6226            && lexical_component_tiers(&name_components, false, &indexed_scope)
6227                .any(|components| components == canonical_cpp_scope_components(candidate))
6228    }) {
6229        return None;
6230    }
6231    Some(node)
6232}
6233
6234fn indexed_scope_matches_target_name(
6235    indexed_scope: &[String],
6236    components: &[String],
6237    global: bool,
6238    target: &CodeUnit,
6239) -> bool {
6240    let target_name = cpp_name_for(target);
6241    lexical_component_tiers(components, global, indexed_scope)
6242        .any(|qualified| qualified.join("::") == target_name)
6243}
6244
6245fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
6246    // A global scope or one surviving name alone cannot distinguish a lost
6247    // namespace from a real global/unrelated reference. Require an enclosing
6248    // declaration component as well as a namespace component matching the
6249    // target's qualified scope.
6250    if target.package_name().is_empty() || indexed_scope.len() <= 1 {
6251        return false;
6252    }
6253    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6254        brokk_bifrost_core::analyzer::Language::Cpp,
6255        &cpp_name_for(target),
6256    );
6257    target_scope.pop();
6258    (1..indexed_scope.len())
6259        .rev()
6260        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
6261}
6262
6263fn indexed_enclosing_lexical_scope(
6264    analyzer: &CppGraphSource<'_>,
6265    file: &ProjectFile,
6266    node: Node<'_>,
6267) -> Option<Vec<String>> {
6268    let range = Range {
6269        start_byte: node.start_byte(),
6270        end_byte: node.end_byte(),
6271        start_line: node.start_position().row,
6272        end_line: node.end_position().row,
6273    };
6274    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6275    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6276        brokk_bifrost_core::analyzer::Language::Cpp,
6277        &cpp_name_for(&enclosing),
6278    );
6279    if !enclosing.is_class() && !enclosing.is_module() {
6280        components.pop();
6281    }
6282    Some(components)
6283}
6284
6285fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6286    if node.kind() != "qualified_identifier" {
6287        return None;
6288    }
6289    let mut stack = vec![node];
6290    while let Some(current) = stack.pop() {
6291        if current.kind() != "qualified_identifier" {
6292            continue;
6293        }
6294        if let Some(scope) = current.child_by_field_name("scope") {
6295            let text = qualified_scope_text(scope, ctx.source);
6296            if name_mentions(&text, &ctx.spec.member_name) {
6297                return Some(scope);
6298            }
6299        }
6300        let mut cursor = current.walk();
6301        for child in current.named_children(&mut cursor) {
6302            if child.kind() == "qualified_identifier" {
6303                stack.push(child);
6304            }
6305        }
6306    }
6307    None
6308}
6309
6310fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6311    let mut parts = vec![node_text(scope, source).to_string()];
6312    let mut current = scope.parent();
6313    while let Some(qualified) = current {
6314        let Some(parent) = qualified.parent() else {
6315            break;
6316        };
6317        if parent.kind() != "qualified_identifier"
6318            || parent.child_by_field_name("name") != Some(qualified)
6319        {
6320            break;
6321        }
6322        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6323            parts.push(node_text(outer_scope, source).to_string());
6324        }
6325        current = Some(parent);
6326    }
6327    parts.reverse();
6328    parts.join("::")
6329}
6330
6331fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6332    if node.kind() == "using_declaration" {
6333        maybe_record_using_callable_hit(node, ctx);
6334        return;
6335    }
6336    if has_ancestor_kind(node, "using_declaration") {
6337        return;
6338    }
6339    if node.kind() == "function_definition" {
6340        return;
6341    }
6342    if !matches!(
6343        node.kind(),
6344        "call_expression"
6345            | "new_expression"
6346            | "compound_literal_expression"
6347            | "declaration"
6348            | "field_initializer"
6349    ) {
6350        return;
6351    }
6352    let Some(owner) = ctx.spec.owner.as_ref() else {
6353        return;
6354    };
6355    if node.kind() == "field_initializer" {
6356        if !field_initializer_constructs_target(node, ctx, owner)
6357            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6358        {
6359            return;
6360        }
6361        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6362            match ctx
6363                .visibility
6364                .call_arity_evidence(ctx.file, node, ctx.source)
6365                .accepts(expected)
6366            {
6367                Some(true) => {}
6368                Some(false) => return,
6369                None => {
6370                    push_unproven_hit(node, ctx);
6371                    return;
6372                }
6373            }
6374        }
6375        match constructor_overload_selection(node, ctx) {
6376            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6377            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6378            ConstructorOverloadSelection::OtherOverload => {}
6379        }
6380        return;
6381    }
6382    if node.kind() == "declaration" {
6383        if declaration_is_object_construction_candidate(node, ctx)
6384            && declaration_mentions_type(node, ctx, owner)
6385            && ctx
6386                .spec
6387                .callable_arity_at(node.start_byte())
6388                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6389        {
6390            // The spelling names the owner in some configuration. The type
6391            // node's structured resolution applies this declaration's guards
6392            // to conditional alias arms, so an arm naming another class is not
6393            // this constructor's site (#6902).
6394            let structured = node.child_by_field_name("type").map(|type_node| {
6395                resolve_type_node_lexically_for_target(
6396                    type_node,
6397                    &ctx.analyzer,
6398                    ctx.visibility,
6399                    &ctx.ordinary_type_imports,
6400                    ctx.file,
6401                    ctx.source,
6402                    owner,
6403                    Some(&ctx.lexical_scope_cache),
6404                    ctx.recovered_sentinel_scope(type_node).as_deref(),
6405                )
6406            });
6407            match structured {
6408                Some(LexicalTypeResolution::Resolved {
6409                    unit, candidates, ..
6410                }) if !same_visible_symbol(&unit, owner)
6411                    && !candidates
6412                        .iter()
6413                        .any(|candidate| same_visible_symbol(candidate, owner)) =>
6414                {
6415                    return;
6416                }
6417                Some(LexicalTypeResolution::Ambiguous) => {
6418                    push_unproven_hit(node, ctx);
6419                    return;
6420                }
6421                Some(LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing)
6422                | None => {}
6423            }
6424            match constructor_overload_selection(node, ctx) {
6425                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6426                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6427                ConstructorOverloadSelection::OtherOverload => {}
6428            }
6429        }
6430        return;
6431    }
6432    let Some(type_node) = constructor_type_node(node) else {
6433        return;
6434    };
6435    let hit_node = function_terminal_node(type_node);
6436    let text = node_text(type_node, ctx.source);
6437    if !name_mentions(text, &ctx.spec.member_name) {
6438        return;
6439    }
6440    *ctx.raw_match_count += 1;
6441    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6442        match ctx
6443            .visibility
6444            .call_arity_evidence(ctx.file, node, ctx.source)
6445            .accepts(expected)
6446        {
6447            Some(true) => {}
6448            Some(false) => return,
6449            None => {
6450                push_unproven_hit(hit_node, ctx);
6451                return;
6452            }
6453        }
6454    }
6455    match constructor_overload_selection(node, ctx) {
6456        ConstructorOverloadSelection::Target => {}
6457        ConstructorOverloadSelection::Ambiguous => {
6458            push_unproven_hit(hit_node, ctx);
6459            return;
6460        }
6461        ConstructorOverloadSelection::OtherOverload => return,
6462    }
6463    let structured_resolution = resolve_type_node_lexically_for_target(
6464        type_node,
6465        &ctx.analyzer,
6466        ctx.visibility,
6467        &ctx.ordinary_type_imports,
6468        ctx.file,
6469        ctx.source,
6470        owner,
6471        Some(&ctx.lexical_scope_cache),
6472        ctx.recovered_sentinel_scope(type_node).as_deref(),
6473    );
6474    let structurally_resolves = matches!(
6475        &structured_resolution,
6476        LexicalTypeResolution::Resolved {
6477            unit, candidates, ..
6478        } if same_visible_symbol(unit, owner)
6479            || candidates
6480                .iter()
6481                .any(|candidate| same_visible_symbol(candidate, owner))
6482    );
6483    if structurally_resolves
6484        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6485            && ctx
6486                .visibility
6487                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6488    {
6489        push_hit(hit_node, ctx);
6490    } else {
6491        push_unproven_hit(hit_node, ctx);
6492    }
6493}
6494
6495/// What the overload filter says about a construction site relative to the
6496/// constructor being scanned.
6497enum ConstructorOverloadSelection {
6498    /// The arguments leave the scan target as the only viable constructor, or
6499    /// the filter has no evidence to apply here. Either way nothing about the
6500    /// arguments argues against the site, so the rest of the scan decides it.
6501    Target,
6502    /// More than one of the owner's constructors stays viable, so the site
6503    /// cannot be proven to belong to the target.
6504    Ambiguous,
6505    /// The arguments select a sibling constructor and not the target.
6506    OtherOverload,
6507}
6508
6509/// Which of the owner's constructors the arguments at a construction site
6510/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6511/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6512/// `T var(args)` declaration.
6513///
6514/// This is the inverse reading of the forward direction's overload choice. The
6515/// candidate set is the owner's arity-compatible constructors, the arguments are
6516/// typed by the same `expression_arg_type` the method and free-function scan
6517/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6518/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6519/// arity alone made a scan of one overload claim every same-arity sibling's call
6520/// (#2908).
6521fn constructor_overload_selection(
6522    node: Node<'_>,
6523    ctx: &ScanCtx<'_>,
6524) -> ConstructorOverloadSelection {
6525    let Some(owner) = ctx.spec.owner.as_ref() else {
6526        return ConstructorOverloadSelection::Target;
6527    };
6528    if ctx.spec.param_types.is_none() {
6529        return ConstructorOverloadSelection::Target;
6530    }
6531    // A `T var(args)` declaration keeps its arguments under the declarator or the
6532    // init declarator's value; every other shape spells an argument list that the
6533    // shared call helpers find, and its argument count needs the macro-aware
6534    // arity evidence.
6535    let (arity, arg_types) = if node.kind() == "declaration" {
6536        match declaration_constructor_initializer(node) {
6537            DeclarationConstructorInitializer::Arguments(arguments) => (
6538                argument_children(arguments).count(),
6539                argument_list_types(arguments, ctx),
6540            ),
6541            DeclarationConstructorInitializer::Expression(value) => {
6542                (1, vec![expression_arg_type(value, ctx)])
6543            }
6544            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6545        }
6546    } else {
6547        let Some(arity) = ctx
6548            .visibility
6549            .call_arity_evidence(ctx.file, node, ctx.source)
6550            .exact()
6551        else {
6552            return ConstructorOverloadSelection::Target;
6553        };
6554        (arity, call_argument_types(node, ctx))
6555    };
6556    let mut candidates = ctx
6557        .visibility
6558        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6559        .into_iter()
6560        .filter(|unit| unit.is_function())
6561        .cloned()
6562        .collect::<Vec<_>>();
6563    candidates.retain(|unit| {
6564        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6565    });
6566    if !candidates
6567        .iter()
6568        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6569    {
6570        return ConstructorOverloadSelection::Target;
6571    }
6572    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6573        candidates,
6574        &arg_types,
6575        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6576        &|candidate| {
6577            ctx.visibility
6578                .callable_is_template_declaration(&ctx.analyzer, candidate)
6579        },
6580        &|name| ctx.visibility.resolve_type(ctx.file, name),
6581        &|left, right| same_visible_symbol(left, right),
6582    );
6583    if !filtered
6584        .iter()
6585        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6586    {
6587        return ConstructorOverloadSelection::OtherOverload;
6588    }
6589    // A header declaration and its out-of-line body are one constructor even
6590    // when their persisted signature strings spell a parameter differently, so
6591    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6592    if filtered.iter().all(|candidate| {
6593        ctx.visibility
6594            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6595    }) {
6596        ConstructorOverloadSelection::Target
6597    } else {
6598        ConstructorOverloadSelection::Ambiguous
6599    }
6600}
6601
6602fn unqualified_base_initializer_constructs_target(
6603    node: Node<'_>,
6604    ctx: &ScanCtx<'_>,
6605    target_owner: &CodeUnit,
6606) -> bool {
6607    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6608        return false;
6609    }
6610    let Some(name) = node
6611        .child_by_field_name("name")
6612        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6613    else {
6614        return false;
6615    };
6616    if node_text(name, ctx.source) != ctx.spec.member_name {
6617        return false;
6618    }
6619    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6620        return false;
6621    };
6622    let inherited = ctx.visibility.inherited_injected_class_owner(
6623        &ctx.analyzer,
6624        ctx.file,
6625        &enclosing_owner,
6626        &ctx.spec.member_name,
6627    );
6628    inherited.is_some_and(|owner| {
6629        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6630    })
6631}
6632
6633fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6634    if node.kind() == "function_definition" {
6635        maybe_record_free_function_definition_hit(node, ctx);
6636        return;
6637    }
6638    if node.kind() == "function_declarator" {
6639        maybe_record_recovered_error_free_function_call(node, ctx);
6640        return;
6641    }
6642    if node.kind() == "identifier" {
6643        maybe_record_free_function_value_reference(node, ctx);
6644        return;
6645    }
6646    if node.kind() != "call_expression" {
6647        return;
6648    }
6649    let Some(function) = node
6650        .child_by_field_name("function")
6651        .or_else(|| node.named_child(0))
6652    else {
6653        return;
6654    };
6655    let text = node_text(function, ctx.source);
6656    if !name_matches_callable(text, &ctx.spec.member_name) {
6657        return;
6658    }
6659    *ctx.raw_match_count += 1;
6660    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6661        match ctx
6662            .visibility
6663            .call_arity_evidence(ctx.file, node, ctx.source)
6664            .accepts(expected)
6665        {
6666            Some(true) => {}
6667            Some(false) => return,
6668            None => {
6669                // The argument count is unknown after macro expansion. It still
6670                // cannot select a *different* target when the bare name binds
6671                // to exactly one visible callable, so let the bare-call
6672                // resolution below prove that site; every other shape stays
6673                // unproven (#1811, the scan side of the same over-conservatism
6674                // that made the forward answer discard its lone candidate).
6675                if !bare_name_binds_only_target(node, function, text, ctx) {
6676                    push_unproven_hit(function_terminal_node(function), ctx);
6677                    return;
6678                }
6679            }
6680        }
6681    }
6682    if matches!(function.kind(), "identifier" | "template_function") {
6683        let terminal = function_terminal_node(function);
6684        let name = node_text(terminal, ctx.source);
6685        if ctx.local_shadows.is_shadowed(name) {
6686            return;
6687        }
6688        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6689            && !matches!(
6690                resolve_declaring_member_owner(
6691                    &ctx.analyzer,
6692                    ctx.visibility,
6693                    ctx.file,
6694                    &enclosing_owner,
6695                    name,
6696                ),
6697                EnclosingMemberOwnerResolution::Missing
6698            )
6699        {
6700            return;
6701        }
6702        match resolve_bare_call_target(
6703            node,
6704            function,
6705            &ctx.analyzer,
6706            ctx.visibility,
6707            &ctx.ordinary_type_imports,
6708            ctx.file,
6709            ctx.source,
6710        ) {
6711            BareCallTargetResolution::FreeFunctions(units)
6712                if units
6713                    .iter()
6714                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6715            {
6716                if free_function_call_may_target(node, text, ctx) {
6717                    let recursive = enclosing_context(terminal, ctx)
6718                        .enclosing
6719                        .as_ref()
6720                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6721                    if recursive {
6722                        push_recursive_reference_hit(terminal, ctx);
6723                    } else {
6724                        push_hit(terminal, ctx);
6725                    }
6726                }
6727            }
6728            BareCallTargetResolution::UnprovenFreeFunctions(units)
6729                if units
6730                    .iter()
6731                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6732            {
6733                push_unproven_hit(terminal, ctx);
6734            }
6735            BareCallTargetResolution::FreeFunctions(_)
6736            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6737            | BareCallTargetResolution::Type(_)
6738            | BareCallTargetResolution::CallableShadow => {}
6739            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6740                push_unproven_hit(terminal, ctx);
6741            }
6742        }
6743        return;
6744    }
6745    if !free_function_call_may_target(node, text, ctx) {
6746        return;
6747    }
6748    if ctx.visibility.contains_named_symbol(
6749        ctx.file,
6750        text,
6751        TargetKind::FreeFunction,
6752        &ctx.spec.target,
6753    ) {
6754        push_hit(function_terminal_node(function), ctx);
6755    } else if ctx.visibility.resolve_known_non_target(
6756        ctx.file,
6757        text,
6758        TargetKind::FreeFunction,
6759        &ctx.spec.target,
6760    ) {
6761        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6762        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6763    } else {
6764        push_unproven_hit(function_terminal_node(function), ctx);
6765    }
6766}
6767
6768fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6769    same_visible_symbol(unit, &ctx.spec.target)
6770        || ctx
6771            .visibility
6772            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6773            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6774                &ctx.analyzer,
6775                ctx.visibility,
6776                unit,
6777                &ctx.spec.target,
6778            )
6779}
6780
6781/// Recover a bare call whose adjacent object-like macro arguments made
6782/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6783///
6784/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6785/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6786/// real block-scope function declaration remains under a `declaration`, so the
6787/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6788/// out of declaration syntax. The malformed parameter list cannot establish
6789/// arity; publish a proven hit only when ordinary visibility leaves one
6790/// callable identity for the bare name.
6791fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6792    // The macro-declaration grammar keeps the same call as a bogus
6793    // `function_definition` whose damaged declarator ends at the block
6794    // literal's `(`: its parameter list is the call's argument prefix, so
6795    // unlike the ERROR shape it carries countable arity evidence.
6796    let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6797    if !block_call_envelope
6798        && (ctx
6799            .ancestry
6800            .parent(node)
6801            .is_none_or(|parent| parent.kind() != "ERROR")
6802            || !has_ancestor_kind(node, "compound_statement"))
6803    {
6804        return;
6805    }
6806    let mut recovered_functions = Vec::new();
6807    if let Some(function) = node
6808        .child_by_field_name("declarator")
6809        .filter(|function| function.kind() == "identifier")
6810    {
6811        recovered_functions.push(function);
6812    }
6813    if let Some(parameters) = node.child_by_field_name("parameters") {
6814        let mut cursor = parameters.walk();
6815        for parameter in parameters
6816            .named_children(&mut cursor)
6817            .filter(|child| child.kind() == "parameter_declaration")
6818        {
6819            if parameter
6820                .child_by_field_name("declarator")
6821                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6822                && let Some(function) = parameter
6823                    .child_by_field_name("type")
6824                    .filter(|function| function.kind() == "type_identifier")
6825            {
6826                recovered_functions.push(function);
6827            }
6828        }
6829    }
6830
6831    for function in recovered_functions {
6832        let name = node_text(function, ctx.source);
6833        if !name_matches_callable(name, &ctx.spec.member_name)
6834            || ctx.local_shadows.is_shadowed(name)
6835        {
6836            continue;
6837        }
6838        *ctx.raw_match_count += 1;
6839        if block_call_envelope
6840            && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6841            && ctx
6842                .visibility
6843                .call_arity_evidence(ctx.file, node, ctx.source)
6844                .accepts(expected)
6845                == Some(false)
6846        {
6847            continue;
6848        }
6849        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6850            push_hit(function, ctx);
6851            continue;
6852        }
6853        let mut candidates =
6854            ctx.visibility
6855                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6856        candidates.retain(|candidate| {
6857            ctx.visibility.declaration_visible_at(
6858                &ctx.analyzer,
6859                ctx.file,
6860                candidate,
6861                function.start_byte(),
6862            )
6863        });
6864        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6865        if candidates
6866            .iter()
6867            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6868        {
6869            push_unproven_hit(function, ctx);
6870        }
6871    }
6872}
6873
6874/// Whether the bare name at `call` binds to exactly one visible callable, and
6875/// that callable is the scan target.
6876///
6877/// This is the scan-side reading of the #1811 rule: with one name binding there
6878/// is nothing an unknown argument count could select instead, so the site is a
6879/// proven reference rather than an unproven one. Only bare identifiers qualify;
6880/// a member or qualified call reaches its target through a receiver this cannot
6881/// judge.
6882fn bare_name_binds_only_target(
6883    call: Node<'_>,
6884    function: Node<'_>,
6885    text: &str,
6886    ctx: &ScanCtx<'_>,
6887) -> bool {
6888    if !matches!(function.kind(), "identifier" | "template_function") {
6889        return false;
6890    }
6891    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6892}
6893
6894fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6895    let mut candidates = ctx
6896        .visibility
6897        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6898    candidates.retain(|candidate| {
6899        ctx.visibility
6900            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6901    });
6902    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6903    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6904}
6905
6906fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6907    if ctx.spec.param_types.is_none() {
6908        return true;
6909    }
6910    let mut candidates = ctx
6911        .visibility
6912        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6913    candidates.retain(|candidate| {
6914        ctx.visibility
6915            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6916    });
6917    callable_candidates_may_match_target(call, candidates, ctx)
6918}
6919
6920fn callable_candidates_may_match_target(
6921    call: Node<'_>,
6922    mut candidates: Vec<CodeUnit>,
6923    ctx: &ScanCtx<'_>,
6924) -> bool {
6925    let Some(arity) = ctx
6926        .visibility
6927        .call_arity_evidence(ctx.file, call, ctx.source)
6928        .exact()
6929    else {
6930        return true;
6931    };
6932    candidates.retain(|unit| {
6933        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6934    });
6935    if candidates.is_empty()
6936        || !candidates
6937            .iter()
6938            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6939    {
6940        return true;
6941    }
6942    let arg_types = call_argument_types(call, ctx);
6943    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6944        candidates,
6945        &arg_types,
6946        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6947        &|candidate| {
6948            ctx.visibility
6949                .callable_is_template_declaration(&ctx.analyzer, candidate)
6950        },
6951        &|name| ctx.visibility.resolve_type(ctx.file, name),
6952        &|left, right| same_visible_symbol(left, right),
6953    );
6954    filtered
6955        .iter()
6956        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6957}
6958
6959/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6960/// in order.
6961///
6962/// A `None` entry is an argument the index cannot type; one of them makes
6963/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6964fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6965    argument_children(arguments)
6966        .map(|arg| expression_arg_type(arg, ctx))
6967        .collect()
6968}
6969
6970fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6971    call_arguments_node(call)
6972        .map(|arguments| argument_list_types(arguments, ctx))
6973        .unwrap_or_default()
6974}
6975
6976fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6977    match node.kind() {
6978        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6979        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6980            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6981            literal
6982        }),
6983        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6984        // `T v(x);` is the vexing parse: the grammar reads it as a function
6985        // declaration whose one parameter is `x` spelled as a type. A parameter
6986        // declaration that is nothing but a type identifier is that reading, and
6987        // the identifier it holds is the constructor argument.
6988        "parameter_declaration" => {
6989            let declared = node.child_by_field_name("type")?;
6990            (node.child_by_field_name("declarator").is_none()
6991                && declared.kind() == "type_identifier")
6992                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6993                .flatten()
6994        }
6995        // `this->m_params` names the same data member as the bare `m_params`.
6996        "field_expression" => {
6997            let receiver = node
6998                .child_by_field_name("argument")
6999                .or_else(|| node.named_child(0))?;
7000            let field = node.child_by_field_name("field")?;
7001            (receiver.kind() == "this")
7002                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
7003                .flatten()
7004        }
7005        "parenthesized_expression" => node
7006            .child_by_field_name("argument")
7007            .or_else(|| node.named_child(0))
7008            .and_then(|inner| expression_arg_type(inner, ctx)),
7009        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
7010        // forward (#2552).
7011        "call_expression" => {
7012            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
7013            expression_arg_type(forwarded, ctx)
7014        }
7015        "pointer_expression" => {
7016            let delta = match node.child_by_field_name("operator")?.kind() {
7017                "&" => 1,
7018                "*" => -1,
7019                _ => return None,
7020            };
7021            let inner = node
7022                .child_by_field_name("argument")
7023                .or_else(|| node.named_child(0))?;
7024            let mut arg_type = expression_arg_type(inner, ctx)?;
7025            arg_type.indirection += delta;
7026            Some(arg_type)
7027        }
7028        _ => None,
7029    }
7030}
7031
7032/// The type of a bare value name used as a call argument: the local or
7033/// parameter binding when one is in scope, otherwise the enclosing class's data
7034/// member of that name.
7035///
7036/// Unqualified lookup reaches a local before a data member, so a local of the
7037/// same name ends the search rather than falling through to the field. This
7038/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
7039/// the two directions agreeing about which overload a member-field argument
7040/// selects (#2894).
7041fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
7042    match ctx.bindings.resolve_symbol(name) {
7043        SymbolResolution::Precise(bindings) => {
7044            bindings.iter().find_map(CppScanBinding::as_arg_type)
7045        }
7046        SymbolResolution::Ambiguous => None,
7047        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
7048            .then(|| enclosing_member_field_arg_type(node, name, ctx))
7049            .flatten(),
7050    }
7051}
7052
7053/// The declared type of `name` read as a data member of the class that lexically
7054/// owns `node`.
7055///
7056/// `None` when there is no enclosing class, when the name is not one data member
7057/// of it, or when the member's declared type does not resolve. An unknown
7058/// argument type makes the overload filter keep every candidate, which is the
7059/// honest answer for a field the index cannot type.
7060fn enclosing_member_field_arg_type(
7061    node: Node<'_>,
7062    name: &str,
7063    ctx: &ScanCtx<'_>,
7064) -> Option<CppArgType> {
7065    let owner = enclosing_context(node, ctx).owner?;
7066    let fields = ctx
7067        .visibility
7068        .visible_members_for_owner_name(ctx.file, &owner, name)
7069        .into_iter()
7070        .filter(|unit| unit.is_field())
7071        .collect::<Vec<_>>();
7072    let [field] = fields.as_slice() else {
7073        return None;
7074    };
7075    let (type_name, unit, indirection) =
7076        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
7077    Some(CppArgType {
7078        name: type_name,
7079        unit,
7080        indirection,
7081        pointee_const: false,
7082    })
7083}
7084
7085/// Record a *non-call* reference to a free function used as a value: `&foo`,
7086/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
7087/// `foo()` is recorded by the call_expression arm, and the function's own
7088/// declaration/definition name is not a reference.
7089fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7090    let text = node_text(node, ctx.source);
7091    if !name_matches_callable(text, &ctx.spec.member_name) {
7092        return;
7093    }
7094    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
7095        *ctx.raw_match_count += 1;
7096        match kind {
7097            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
7098            RecoveredCallableDeclarationKind::Definition => {
7099                push_recovered_definition_hit(node, ctx)
7100            }
7101        }
7102        return;
7103    }
7104    if is_declaration_name(node) {
7105        maybe_record_free_function_declaration_reference(node, ctx);
7106        return;
7107    }
7108    if is_call_callee_node(node) {
7109        return;
7110    }
7111    *ctx.raw_match_count += 1;
7112    if ctx.visibility.contains_named_symbol(
7113        ctx.file,
7114        text,
7115        TargetKind::FreeFunction,
7116        &ctx.spec.target,
7117    ) {
7118        push_hit(node, ctx);
7119    } else if ctx.visibility.resolve_known_non_target(
7120        ctx.file,
7121        text,
7122        TargetKind::FreeFunction,
7123        &ctx.spec.target,
7124    ) {
7125        // A qualified reference proven to a different namespace is not a match.
7126    } else {
7127        push_unproven_hit(node, ctx);
7128    }
7129}
7130
7131#[derive(Clone, Copy)]
7132enum RecoveredCallableDeclarationKind {
7133    Declaration,
7134    Definition,
7135}
7136
7137/// Recover the role of a callable name whose declaration was split by an
7138/// ERROR node. An indexed declaration range proves identity when available;
7139/// for a top-level C callable that recovery failed to range-index, same-file
7140/// name identity is sufficient because C has no function overloading. The CST
7141/// shape distinguishes a prototype from a definition. C++ parsing of
7142/// an unknown C return type can put the name under an ERROR child of the
7143/// function declarator, while call-shaped recovery puts that declarator under
7144/// an ERROR whose parent is a compound statement; only the former is admitted.
7145fn recovered_callable_declaration_kind(
7146    node: Node<'_>,
7147    ctx: &ScanCtx<'_>,
7148) -> Option<RecoveredCallableDeclarationKind> {
7149    if !matches!(node.kind(), "identifier" | "field_identifier") {
7150        return None;
7151    }
7152    let target_identity_proven =
7153        ctx.target_declaration_ranges.iter().any(|range| {
7154            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7155        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
7156            && ctx.spec.target.is_function()
7157            && ctx.spec.target.source() == ctx.file);
7158    let mut current = ctx.ancestry.parent(node);
7159    while let Some(parent) = current {
7160        if parent.is_error() {
7161            // C files with an unknown return type can retain the real
7162            // callable name as `function_declarator > ERROR`, with the ERROR
7163            // as a named child rather than the declarator field itself.
7164            let error = parent;
7165            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
7166                function.kind() == "function_declarator"
7167                    && function
7168                        .child_by_field_name("parameters")
7169                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
7170            }) else {
7171                current = ctx.ancestry.parent(parent);
7172                continue;
7173            };
7174            let container = ctx.ancestry.parent(function)?;
7175            let kind = match container.kind() {
7176                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7177                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7178                _ => return None,
7179            };
7180            return target_identity_proven.then_some(kind);
7181        }
7182        if parent.kind() == "function_declarator" {
7183            let error = ctx
7184                .ancestry
7185                .parent(parent)
7186                .filter(|parent| parent.is_error())?;
7187            let declarator = parent.child_by_field_name("declarator")?;
7188            if declarator.start_byte() > node.start_byte()
7189                || node.end_byte() > declarator.end_byte()
7190            {
7191                return None;
7192            }
7193            let container = ctx.ancestry.parent(error)?;
7194            let kind = match container.kind() {
7195                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7196                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7197                _ => return None,
7198            };
7199            return target_identity_proven.then_some(kind);
7200        }
7201        if parent.is_error() {
7202            current = ctx.ancestry.parent(parent);
7203            continue;
7204        }
7205        if matches!(
7206            parent.kind(),
7207            "translation_unit" | "function_definition" | "compound_statement"
7208        ) {
7209            return None;
7210        }
7211        current = ctx.ancestry.parent(parent);
7212    }
7213    None
7214}
7215
7216fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7217    if !ctx.spec.callable_has_definition_body {
7218        return;
7219    }
7220    let text = node_text(node, ctx.source);
7221    if !name_matches_callable(text, &ctx.spec.member_name) {
7222        return;
7223    }
7224    let mut declaration = ctx.ancestry.parent(node);
7225    while let Some(candidate) = declaration {
7226        if candidate.kind() == "function_definition" {
7227            return;
7228        }
7229        if candidate.kind() == "declaration" {
7230            break;
7231        }
7232        declaration = ctx.ancestry.parent(candidate);
7233    }
7234    if declaration.is_none() {
7235        return;
7236    }
7237    let linked_declaration = ctx
7238        .visibility
7239        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
7240        .into_iter()
7241        .find(|candidate| {
7242            candidate.source() == ctx.file
7243                && candidate.is_function()
7244                && (!ctx.target_group.contains(candidate)
7245                    || candidate.source() == ctx.spec.target.source())
7246                && ctx.analyzer.ranges(candidate).iter().any(|range| {
7247                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7248                })
7249                && (candidate.source() == ctx.spec.target.source()
7250                    && candidate.fq_name() == ctx.spec.target.fq_name()
7251                    && candidate.signature() == ctx.spec.target.signature()
7252                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
7253                        &ctx.analyzer,
7254                        ctx.visibility,
7255                        candidate,
7256                        &ctx.spec.target,
7257                    ))
7258                && callable_arity_matches_target(candidate, node, ctx)
7259        });
7260    if linked_declaration.is_none() {
7261        return;
7262    }
7263    *ctx.raw_match_count += 1;
7264    push_declaration_reference_hit(node, ctx);
7265}
7266
7267fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7268    let Some(function) = function_definition_name_node(node) else {
7269        return;
7270    };
7271    let text = node_text(function, ctx.source);
7272    if !name_matches_callable(text, &ctx.spec.member_name) {
7273        return;
7274    }
7275    *ctx.raw_match_count += 1;
7276    if !function_definition_signature_matches_target(node, ctx) {
7277        return;
7278    }
7279    match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
7280        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7281        DefinitionNameTargetResolution::KnownNonTarget => {
7282            // A definition in another explicit namespace is a proven non-match.
7283        }
7284        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7285    }
7286}
7287
7288fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7289    if node.kind() == "preproc_arg" {
7290        maybe_record_function_macro_replacement_method_hits(node, ctx);
7291        return;
7292    }
7293    if node.kind() == "using_declaration" {
7294        maybe_record_using_callable_hit(node, ctx);
7295        return;
7296    }
7297    if has_ancestor_kind(node, "using_declaration") {
7298        return;
7299    }
7300    if node.kind() == "function_definition" {
7301        maybe_record_method_definition_hit(node, ctx);
7302        return;
7303    }
7304    if is_declaration_name(node) {
7305        return;
7306    }
7307    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7308        maybe_record_qualified_method_value_hit(node, member, ctx);
7309        return;
7310    }
7311    if let Some(value) = qualified_callable_value(node) {
7312        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7313        return;
7314    }
7315    if let Some(call) = recovered_relational_template_member_call(node) {
7316        maybe_record_recovered_relational_template_method_hit(call, ctx);
7317        return;
7318    }
7319    if node.kind() != "call_expression" {
7320        return;
7321    }
7322    if let Some((receiver, operator)) = explicit_operator_call(node) {
7323        let text = node_text(operator, ctx.source);
7324        if !name_matches_callable(text, &ctx.spec.member_name) {
7325            return;
7326        }
7327        *ctx.raw_match_count += 1;
7328        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7329            match ctx
7330                .visibility
7331                .call_arity_evidence(ctx.file, node, ctx.source)
7332                .accepts(expected)
7333            {
7334                Some(true) => {}
7335                Some(false) => return,
7336                None => {
7337                    push_unproven_hit(operator, ctx);
7338                    return;
7339                }
7340            }
7341        }
7342        let arity = ctx
7343            .visibility
7344            .call_arity_evidence(ctx.file, node, ctx.source)
7345            .exact();
7346        record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7347        return;
7348    }
7349    let Some(function) = node
7350        .child_by_field_name("function")
7351        .or_else(|| node.named_child(0))
7352    else {
7353        return;
7354    };
7355    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7356        return;
7357    }
7358    if function.kind() == "identifier"
7359        && ctx
7360            .local_shadows
7361            .is_shadowed(node_text(function, ctx.source))
7362    {
7363        return;
7364    }
7365    *ctx.raw_match_count += 1;
7366    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7367        match ctx
7368            .visibility
7369            .call_arity_evidence(ctx.file, node, ctx.source)
7370            .accepts(expected)
7371        {
7372            Some(true) => {}
7373            Some(false) => return,
7374            None => {
7375                push_unproven_hit(function_terminal_node(function), ctx);
7376                return;
7377            }
7378        }
7379    }
7380    if !method_call_may_target(node, ctx) {
7381        return;
7382    }
7383    if is_structurally_qualified(function) {
7384        match qualified_owner_resolution(function, ctx) {
7385            QualifiedOwnerResolution::Target => {
7386                push_hit(function_terminal_node(function), ctx);
7387            }
7388            QualifiedOwnerResolution::NonTarget => {}
7389            QualifiedOwnerResolution::Unresolved => {
7390                push_unproven_hit(function_terminal_node(function), ctx);
7391            }
7392        }
7393        return;
7394    }
7395    match call_function_target_resolution(function, ctx) {
7396        MethodReceiverTargetResolution::Target
7397            if call_function_has_direct_self_receiver(
7398                function,
7399                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7400            ) =>
7401        {
7402            push_self_receiver_hit(function_terminal_node(function), ctx);
7403        }
7404        MethodReceiverTargetResolution::Target => {
7405            push_hit(function_terminal_node(function), ctx);
7406        }
7407        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7408        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7409        // the target member declared on a base is a genuine external usage of that inherited
7410        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7411        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7412        // arm because `same_owner_context` also accepts this inherited case.
7413        MethodReceiverTargetResolution::Missing
7414            if inherited_target_owner_context(function, ctx) =>
7415        {
7416            push_hit(function_terminal_node(function), ctx);
7417        }
7418        MethodReceiverTargetResolution::Missing
7419            if (matches!(function.kind(), "identifier" | "template_function")
7420                || call_function_has_direct_self_receiver(
7421                    function,
7422                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7423                ))
7424                && (same_owner_context(function, ctx)
7425                    || out_of_line_target_owner_context(function, ctx)) =>
7426        {
7427            push_self_receiver_hit(function_terminal_node(function), ctx);
7428        }
7429        MethodReceiverTargetResolution::Missing
7430            if function.kind() == "identifier"
7431                && resolves_to_lexical_free_function(function, ctx) =>
7432        {
7433            // A visible namespace/free function is a proven negative once the
7434            // enclosing structured owner and its hierarchy contain no such member.
7435        }
7436        MethodReceiverTargetResolution::Missing
7437            if !receiver_has_known_non_target(function, ctx)
7438                && !known_non_target_owner_context(function, ctx) =>
7439        {
7440            push_unproven_hit(function_terminal_node(function), ctx);
7441        }
7442        MethodReceiverTargetResolution::Missing => {}
7443    }
7444}
7445
7446/// Report member calls written inside a function-like macro's replacement.
7447///
7448/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7449/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7450/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7451/// structure, and every hit is reported at the bytes the member spells inside
7452/// that token.
7453fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7454    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7455        parent.kind() == "preproc_function_def"
7456            && parent
7457                .child_by_field_name("value")
7458                .is_some_and(|value| same_node(value, node))
7459    }) else {
7460        return;
7461    };
7462    let Some(body) = ctx
7463        .visibility
7464        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7465    else {
7466        return;
7467    };
7468    let Some(statements) = body.statements() else {
7469        return;
7470    };
7471    // Collect the candidate calls before typing anything: a replacement that
7472    // never names the queried member must not pay for its declarations.
7473    let mut candidates = Vec::new();
7474    let mut stack = vec![statements];
7475    while let Some(current) = stack.pop() {
7476        push_named_children_reversed(current, &mut stack);
7477        if current.kind() != "call_expression" {
7478            continue;
7479        }
7480        let Some(function) = current
7481            .child_by_field_name("function")
7482            .filter(|function| function.kind() == "field_expression")
7483        else {
7484            continue;
7485        };
7486        let Some(member) = function.child_by_field_name("field") else {
7487            continue;
7488        };
7489        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7490            continue;
7491        }
7492        let Some(receiver) = function
7493            .child_by_field_name("argument")
7494            .or_else(|| function.child_by_field_name("object"))
7495            .or_else(|| function.named_child(0))
7496        else {
7497            continue;
7498        };
7499        candidates.push((current, member, receiver));
7500    }
7501    if candidates.is_empty() {
7502        return;
7503    }
7504    let Some(replacement_start) =
7505        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7506    else {
7507        return;
7508    };
7509    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7510    for (current, member, receiver) in candidates {
7511        if *ctx.limit_exceeded {
7512            return;
7513        }
7514        let range = body.file_range(member, replacement_start);
7515        debug_assert_eq!(
7516            ctx.source.get(range.clone()),
7517            Some(node_text(member, &body.source)),
7518            "macro replacement member range must spell the member name"
7519        );
7520        *ctx.raw_match_count += 1;
7521        // The replacement's own macro environment is the one at the definition,
7522        // not at the sentinel offsets the recovered call carries.
7523        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7524            ctx.file,
7525            current,
7526            &body.source,
7527            definition.start_byte(),
7528        );
7529        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7530            match arity_evidence.accepts(expected) {
7531                Some(true) => {}
7532                Some(false) => continue,
7533                None => {
7534                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7535                    continue;
7536                }
7537            }
7538        }
7539        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7540        {
7541            MacroReplacementReceiver::Parameter => {
7542                if target_member_is_visible_candidate(ctx) {
7543                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7544                }
7545                continue;
7546            }
7547            MacroReplacementReceiver::Unknown => continue,
7548            MacroReplacementReceiver::Units(units) => {
7549                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7550            }
7551        };
7552        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7553            MethodReceiverTargetResolution::Target => {
7554                push_reference_hit_range(node, range.start, range.end, ctx);
7555            }
7556            MethodReceiverTargetResolution::Missing => {
7557                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7558            }
7559            MethodReceiverTargetResolution::NonTarget
7560            | MethodReceiverTargetResolution::Ambiguous => {}
7561        }
7562    }
7563}
7564
7565enum MacroReplacementReceiver {
7566    /// The receiver is one of the macro's parameters. Its type is supplied by
7567    /// each invocation, so a member call on it can never be proven here.
7568    Parameter,
7569    /// The receiver's declared type resolved to these units.
7570    Units(Vec<CodeUnit>),
7571    /// The receiver has a shape this path does not type.
7572    Unknown,
7573}
7574
7575fn macro_replacement_receiver(
7576    receiver: Node<'_>,
7577    body: &ParsedReplacementBody,
7578    locals: &HashMap<String, Option<CodeUnit>>,
7579    anchor: Node<'_>,
7580    ctx: &ScanCtx<'_>,
7581) -> MacroReplacementReceiver {
7582    let mut current = receiver;
7583    while matches!(
7584        current.kind(),
7585        "parenthesized_expression" | "pointer_expression"
7586    ) {
7587        let Some(inner) = current
7588            .child_by_field_name("argument")
7589            .or_else(|| current.named_child(0))
7590        else {
7591            return MacroReplacementReceiver::Unknown;
7592        };
7593        current = inner;
7594    }
7595    if !matches!(current.kind(), "identifier" | "field_identifier") {
7596        return MacroReplacementReceiver::Unknown;
7597    }
7598    let name = node_text(current, &body.source);
7599    if body.parameters.iter().any(|parameter| parameter == name) {
7600        return MacroReplacementReceiver::Parameter;
7601    }
7602    if let Some(local) = locals.get(name) {
7603        return match local {
7604            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7605            None => MacroReplacementReceiver::Unknown,
7606        };
7607    }
7608    // Neither a parameter nor a replacement-local: an ordinary name the macro
7609    // definition site sees, resolved exactly as the ordinary receiver path
7610    // resolves a bare identifier.
7611    let global_fields = ctx
7612        .visibility
7613        .visible_identifier_candidates(ctx.file, name)
7614        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7615        .collect::<Vec<_>>();
7616    if !global_fields.is_empty() {
7617        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7618            global_fields,
7619            anchor,
7620            ctx,
7621        ));
7622    }
7623    MacroReplacementReceiver::Units(
7624        ctx.visibility
7625            .resolve_type(ctx.file, name)
7626            .into_iter()
7627            .collect(),
7628    )
7629}
7630
7631/// Type the locals a macro replacement declares, keyed by declared name.
7632///
7633/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7634/// serves: the receiver's type is stated inside the replacement itself. A name
7635/// whose declared type does not resolve is kept with no unit, so a call on it
7636/// stays silent instead of falling through to file-scope name lookup.
7637fn macro_replacement_local_receivers(
7638    statements: Node<'_>,
7639    body: &ParsedReplacementBody,
7640    ctx: &ScanCtx<'_>,
7641) -> HashMap<String, Option<CodeUnit>> {
7642    let mut locals = HashMap::default();
7643    let mut stack = vec![statements];
7644    while let Some(current) = stack.pop() {
7645        push_named_children_reversed(current, &mut stack);
7646        if current.kind() != "declaration" {
7647            continue;
7648        }
7649        let Some(type_node) = current
7650            .child_by_field_name("type")
7651            .or_else(|| first_type_child(current))
7652        else {
7653            continue;
7654        };
7655        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7656        let mut cursor = current.walk();
7657        for child in current.named_children(&mut cursor) {
7658            let declarator = if child.kind() == "init_declarator" {
7659                child.child_by_field_name("declarator")
7660            } else {
7661                is_declarator_node(child).then_some(child)
7662            };
7663            let Some(name) =
7664                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7665            else {
7666                continue;
7667            };
7668            locals.insert(name, unit.clone());
7669        }
7670    }
7671    locals
7672}
7673
7674fn macro_replacement_declared_unit(
7675    type_node: Node<'_>,
7676    body: &ParsedReplacementBody,
7677    ctx: &ScanCtx<'_>,
7678) -> Option<CodeUnit> {
7679    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7680    let unit = match ctx
7681        .visibility
7682        .resolve_type_node_result(ctx.file, type_node, &body.source)
7683    {
7684        Ok(Some(unit)) => Some(unit),
7685        Ok(None) => ctx
7686            .visibility
7687            .canonical_type_for_reference(ctx.file, &name)
7688            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7689        Err(_) => None,
7690    }?;
7691    canonical_receiver_unit(&unit, ctx)
7692}
7693
7694/// Whether the queried member is a candidate this file can see.
7695///
7696/// A macro parameter receiver has no type at the definition, so a member call
7697/// on it is at most unproven. Requiring the target to be visible here keeps a
7698/// same-named member of an unrelated translation unit out of the result.
7699fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7700    ctx.visibility
7701        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7702        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7703}
7704
7705fn maybe_record_recovered_relational_template_method_hit(
7706    call: RecoveredRelationalTemplateMemberCall<'_>,
7707    ctx: &mut ScanCtx<'_>,
7708) {
7709    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7710        return;
7711    }
7712    *ctx.raw_match_count += 1;
7713    if !ctx
7714        .visibility
7715        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7716        || ctx
7717            .spec
7718            .callable_arity_at(call.member.start_byte())
7719            .is_some_and(|arity| !arity.accepts(call.arity))
7720    {
7721        return;
7722    }
7723    record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7724}
7725
7726fn record_explicit_receiver_method_hit(
7727    receiver: Node<'_>,
7728    member: Node<'_>,
7729    arity: Option<usize>,
7730    ctx: &mut ScanCtx<'_>,
7731) {
7732    match explicit_receiver_target_resolution(receiver, arity, ctx) {
7733        MethodReceiverTargetResolution::Target
7734            if receiver_is_self_like(
7735                receiver,
7736                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7737            ) =>
7738        {
7739            push_self_receiver_hit(member, ctx);
7740        }
7741        MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7742        MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7743        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7744    }
7745}
7746
7747fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7748    if node.kind() != "qualified_identifier" {
7749        return None;
7750    }
7751    let parameter = node
7752        .parent()
7753        .filter(|parent| parent.kind() == "parameter_declaration")?;
7754    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7755    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7756    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7757    // and `(arg)` as an abstract function declarator. Ordinary qualified
7758    // parameter types have named/pointer/reference declarators instead.
7759    if parameter.child_by_field_name("type") != Some(node)
7760        || parameter_declarator.kind() != "abstract_function_declarator"
7761    {
7762        return None;
7763    }
7764    let parameter_list = parameter
7765        .parent()
7766        .filter(|parent| parent.kind() == "parameter_list")?;
7767    if parameter_list.named_child_count() != 1 {
7768        return None;
7769    }
7770    let function_declarator = parameter_list
7771        .parent()
7772        .filter(|parent| parent.kind() == "function_declarator")?;
7773    if function_declarator
7774        .child_by_field_name("declarator")
7775        .is_none_or(|declarator| declarator.kind() != "identifier")
7776        || function_declarator
7777            .parent()
7778            .is_none_or(|parent| parent.kind() != "declaration")
7779    {
7780        return None;
7781    }
7782    node.child_by_field_name("name")
7783}
7784
7785fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7786    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7787        return;
7788    };
7789    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7790        return;
7791    }
7792    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7793        return;
7794    };
7795    *ctx.raw_match_count += 1;
7796    let owner_resolution = qualified_owner_components(imported, ctx.source)
7797        .map(|qualified| {
7798            let lexical_scope = match enclosing_lexical_scope_components(
7799                imported,
7800                &ctx.analyzer,
7801                ctx.visibility,
7802                ctx.file,
7803                ctx.source,
7804            ) {
7805                LexicalScopeResolution::Resolved(scope) => scope,
7806                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7807                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7808            };
7809            ctx.visibility.resolve_type_components_lexically(
7810                &ctx.analyzer,
7811                ctx.file,
7812                &qualified.names,
7813                qualified.global,
7814                &lexical_scope,
7815            )
7816        })
7817        .unwrap_or(LexicalTypeResolution::Missing);
7818    let matches_target_owner = matches!(
7819        owner_resolution,
7820        LexicalTypeResolution::Resolved {
7821            ref unit,
7822            ref candidates,
7823            ..
7824        } if same_visible_symbol(unit, target_owner)
7825            || candidates
7826                .iter()
7827                .any(|candidate| same_visible_symbol(candidate, target_owner))
7828    );
7829    if !matches_target_owner {
7830        match owner_resolution {
7831            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7832                push_unproven_hit(imported, ctx);
7833            }
7834            LexicalTypeResolution::Resolved { .. } => {}
7835        }
7836        return;
7837    }
7838    match visible_target_callable_member_resolution(target_owner, ctx) {
7839        VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7840        VisibleTargetCallableMemberResolution::NonCallable => {}
7841        VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7842    }
7843}
7844
7845enum VisibleTargetCallableMemberResolution {
7846    Target,
7847    NonCallable,
7848    Unresolved,
7849}
7850
7851fn visible_target_callable_member_resolution(
7852    owner: &CodeUnit,
7853    ctx: &ScanCtx<'_>,
7854) -> VisibleTargetCallableMemberResolution {
7855    match ctx
7856        .visibility
7857        .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7858    {
7859        VisibleMemberResolution::Callable(candidates)
7860            if candidates.iter().all(|candidate| {
7861                ctx.target_group.contains(candidate)
7862                    || ctx
7863                        .target_group
7864                        .iter()
7865                        .any(|target| same_visible_symbol(candidate, target))
7866            }) =>
7867        {
7868            VisibleTargetCallableMemberResolution::Target
7869        }
7870        VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7871        VisibleMemberResolution::Callable(_)
7872        | VisibleMemberResolution::AmbiguousKind
7873        | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7874    }
7875}
7876
7877fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7878    let name = node_text(node, ctx.source);
7879    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7880    let key = (namespace.clone(), name.to_string());
7881    if let Some(resolved) = ctx
7882        .lexical_free_function_cache
7883        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
7884            cells.borrow().get(&key).copied()
7885        })
7886    {
7887        return resolved;
7888    }
7889    let resolved = ctx
7890        .visibility
7891        .visible_identifier_candidates(ctx.file, name)
7892        .any(|unit| {
7893            unit.is_function()
7894                && type_owner_of(&ctx.analyzer, unit).is_none()
7895                && unit.package_name() == namespace
7896        });
7897    ctx.lexical_free_function_cache
7898        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
7899            cells.borrow_mut().insert(key, resolved)
7900        });
7901    resolved
7902}
7903
7904fn maybe_record_qualified_method_value_hit(
7905    qualified: Node<'_>,
7906    member: Node<'_>,
7907    ctx: &mut ScanCtx<'_>,
7908) {
7909    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7910        return;
7911    }
7912    *ctx.raw_match_count += 1;
7913    let resolution =
7914        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7915    match resolution {
7916        LexicalCallableValueResolution::Type(resolved_owner) => {
7917            let Some(owner) = ctx.spec.owner.as_ref() else {
7918                push_unproven_hit(member, ctx);
7919                return;
7920            };
7921            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7922                if same_visible_symbol(&resolved_owner, owner) {
7923                    push_unproven_hit(member, ctx);
7924                }
7925                return;
7926            }
7927            // An explicitly qualified method value remains an external
7928            // reference even when its owner is the enclosing class.
7929            match visible_target_callable_member_resolution(owner, ctx) {
7930                VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7931                VisibleTargetCallableMemberResolution::NonCallable => {}
7932                VisibleTargetCallableMemberResolution::Unresolved => {
7933                    push_unproven_hit(member, ctx);
7934                }
7935            }
7936        }
7937        LexicalCallableValueResolution::FreeFunction(_) => {}
7938        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7939            push_unproven_hit(member, ctx);
7940        }
7941    }
7942}
7943
7944fn qualified_callable_value_resolution(
7945    qualified: Node<'_>,
7946    member_name: &str,
7947    ctx: &ScanCtx<'_>,
7948) -> LexicalCallableValueResolution {
7949    let Some((owner_components, global)) =
7950        qualified_callable_owner_components(qualified, ctx.source)
7951    else {
7952        return LexicalCallableValueResolution::Missing;
7953    };
7954    let lexical_scope = if global {
7955        Vec::new()
7956    } else {
7957        match enclosing_lexical_scope_components(
7958            qualified,
7959            &ctx.analyzer,
7960            ctx.visibility,
7961            ctx.file,
7962            ctx.source,
7963        ) {
7964            LexicalScopeResolution::Resolved(scope) => scope,
7965            LexicalScopeResolution::Ambiguous => {
7966                return LexicalCallableValueResolution::Ambiguous;
7967            }
7968            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7969        }
7970    };
7971    if let Some(target_owner) = ctx.spec.owner.as_ref()
7972        && let LexicalTypeResolution::Resolved { unit, .. } =
7973            resolve_type_components_lexically_at_for_target_with_scope_cache(
7974                qualified,
7975                &owner_components,
7976                global,
7977                &ctx.analyzer,
7978                ctx.visibility,
7979                &ctx.ordinary_type_imports,
7980                ctx.file,
7981                ctx.source,
7982                target_owner,
7983                false,
7984                Some(&ctx.lexical_scope_cache),
7985            )
7986    {
7987        return LexicalCallableValueResolution::Type(unit);
7988    }
7989    ctx.visibility.resolve_callable_value_components_lexically(
7990        &ctx.analyzer,
7991        ctx.file,
7992        &owner_components,
7993        member_name,
7994        global,
7995        &lexical_scope,
7996    )
7997}
7998
7999fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8000    let Some(owner) = ctx.spec.owner.as_ref() else {
8001        return true;
8002    };
8003    if ctx.spec.param_types.is_none() {
8004        return true;
8005    }
8006    let candidates = ctx
8007        .visibility
8008        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
8009        .into_iter()
8010        .filter(|unit| unit.is_function())
8011        .cloned()
8012        .collect::<Vec<_>>();
8013    callable_candidates_may_match_target(call, candidates, ctx)
8014}
8015
8016fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8017    let Some(function) = function_definition_name_node(node) else {
8018        return;
8019    };
8020    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
8021        return;
8022    }
8023    *ctx.raw_match_count += 1;
8024    if !function_definition_signature_matches_target(node, ctx) {
8025        return;
8026    }
8027    if node_inside_target_declaration(function, ctx) {
8028        return;
8029    }
8030    if is_structurally_qualified(function) {
8031        match qualified_owner_resolution(function, ctx) {
8032            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
8033            QualifiedOwnerResolution::NonTarget => {}
8034            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8035        }
8036        return;
8037    }
8038    match definition_name_target_resolution(function, TargetKind::Method, ctx) {
8039        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
8040        DefinitionNameTargetResolution::KnownNonTarget => {
8041            // A method definition for another visible owner is a proven non-match.
8042        }
8043        DefinitionNameTargetResolution::Unresolved
8044            if known_non_target_owner_context(function, ctx) => {}
8045        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8046    }
8047}
8048
8049pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8050    let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
8051    ctx.target_declaration_ranges
8052        .iter()
8053        .any(|range| range.contains(&node_range))
8054}
8055
8056fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
8057    let mut receiver = None;
8058    let mut cursor = node.walk();
8059    for child in node.named_children(&mut cursor) {
8060        if child.kind() == "argument_list" {
8061            continue;
8062        }
8063        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
8064            return receiver.map(|receiver| (receiver, operator));
8065        }
8066        if receiver.is_none() {
8067            receiver = Some(child);
8068        }
8069    }
8070    None
8071}
8072
8073/// Return the name node of a C++ function definition's declarator.
8074pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
8075    if node.kind() != "function_definition" {
8076        return None;
8077    }
8078    node.child_by_field_name("declarator")
8079        .and_then(declarator_name_node)
8080}
8081
8082fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
8083    function_definition_name_node(node)
8084}
8085
8086fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8087    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8088        return true;
8089    };
8090    let Some(candidate) = canonical_callable_at_node(
8091        node,
8092        &ctx.analyzer,
8093        ctx.visibility,
8094        ctx.file,
8095        ctx.source,
8096        ctx.spec.kind,
8097    ) else {
8098        return false;
8099    };
8100    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
8101        return false;
8102    };
8103    if !callable_arity_matches(expected, actual_arity) {
8104        return false;
8105    }
8106    ctx.spec.param_types.as_ref().is_none_or(|expected| {
8107        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
8108            .is_some_and(|actual| actual == *expected)
8109    })
8110}
8111
8112fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8113    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8114        return true;
8115    };
8116    cpp_callable_arity(&ctx.analyzer, candidate)
8117        .is_some_and(|actual| callable_arity_matches(expected, actual))
8118}
8119
8120fn callable_arity_matches(
8121    expected: brokk_bifrost_core::analyzer::model::CallableArity,
8122    actual: brokk_bifrost_core::analyzer::model::CallableArity,
8123) -> bool {
8124    expected.accepts(actual.total())
8125}
8126
8127fn canonical_callable_at_node(
8128    node: Node<'_>,
8129    analyzer: &CppGraphSource<'_>,
8130    visibility: &VisibilityIndex<'_>,
8131    file: &ProjectFile,
8132    source: &str,
8133    kind: TargetKind,
8134) -> Option<CodeUnit> {
8135    let name = callable_name_node(node)?;
8136    let name_range = (name.start_byte(), name.end_byte());
8137    let name_text = node_text(name, source);
8138    for candidate in visibility.named_candidates(file, name_text, kind) {
8139        if candidate.source() != file || !candidate.is_function() {
8140            continue;
8141        }
8142        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
8143            continue;
8144        };
8145        for occurrence in occurrences {
8146            if occurrence
8147                .name_range
8148                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
8149            {
8150                return Some(candidate);
8151            }
8152        }
8153    }
8154    None
8155}
8156
8157fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
8158    let name = if node.kind() == "function_definition" {
8159        function_definition_name_node(node)?
8160    } else {
8161        node.child_by_field_name("declarator")
8162            .and_then(declarator_name_node)?
8163    };
8164    Some(function_terminal_node(name))
8165}
8166
8167fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
8168    name_matches_callable(node_text(function_terminal_node(node), source), expected)
8169}
8170
8171enum DefinitionNameTargetResolution {
8172    Target,
8173    KnownNonTarget,
8174    Unresolved,
8175}
8176
8177fn definition_name_target_resolution(
8178    function: Node<'_>,
8179    target_kind: TargetKind,
8180    ctx: &ScanCtx<'_>,
8181) -> DefinitionNameTargetResolution {
8182    let candidates = definition_name_candidates(function, ctx);
8183    if candidates
8184        .iter()
8185        .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
8186        .any(|name| {
8187            ctx.visibility
8188                .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
8189        })
8190    {
8191        DefinitionNameTargetResolution::Target
8192    } else if candidates.iter().any(|name| {
8193        ctx.visibility
8194            .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
8195    }) {
8196        DefinitionNameTargetResolution::KnownNonTarget
8197    } else {
8198        DefinitionNameTargetResolution::Unresolved
8199    }
8200}
8201
8202fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
8203    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
8204    if raw.is_empty() {
8205        return Vec::new();
8206    }
8207    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
8208        return vec![raw];
8209    };
8210    if !raw.contains("::") {
8211        return vec![format!("{namespace}::{raw}")];
8212    }
8213    // fqname-M4: peeks at the raw first `::`-split token, including the empty
8214    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
8215    // shape as rust's `rust_reference_looks_external`); the shared structured
8216    // splitter filters empty segments, which would shift "which token is
8217    // first" for that one lead-`::` shape and is not proven equivalent here.
8218    if raw
8219        .split("::")
8220        .next()
8221        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
8222    {
8223        vec![format!("{namespace}::{raw}"), raw]
8224    } else {
8225        vec![raw]
8226    }
8227}
8228
8229fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8230    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
8231}
8232
8233/// Whether a name leaf the field scans reached sits in a value position.
8234///
8235/// Every kind those scans admit already names a value except `type_identifier`.
8236/// tree-sitter spells a non-type template argument with type syntax, so the
8237/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
8238/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
8239/// Admit that leaf only where no visible type explains the spelling, which is
8240/// the order forward navigation applies before it reads the leaf in the value
8241/// namespace (#2556). Every other `type_identifier` is a type reference and
8242/// belongs to the type scan.
8243fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8244    node.kind() != "type_identifier"
8245        || (is_type_shaped_template_argument_name(node)
8246            && ctx
8247                .visibility
8248                .resolve_type(ctx.file, node_text(node, ctx.source))
8249                .is_none())
8250}
8251
8252fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8253    if is_c_offsetof_member_node(node) {
8254        return;
8255    }
8256    if matches!(node.kind(), "identifier" | "field_identifier")
8257        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8258            .is_some()
8259    {
8260        return;
8261    }
8262    if !matches!(
8263        node.kind(),
8264        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
8265    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8266        || !scan_leaf_is_value_position(node, ctx)
8267        || is_declaration_name(node)
8268        || is_member_field_own_declarator(node, ctx)
8269        || is_selected_field_expression_member_descendant(node)
8270        || is_nested_in_qualified_identifier(node)
8271    {
8272        return;
8273    }
8274    *ctx.raw_match_count += 1;
8275    if global_field_resolves_to_target(node, ctx) {
8276        push_hit(node, ctx);
8277    } else if global_field_is_known_non_target(node, ctx) {
8278    } else {
8279        push_unproven_hit(node, ctx);
8280    }
8281}
8282
8283/// Whether `node` belongs to the selected-member side of any enclosing field
8284/// expression. A reference may be nested arbitrarily inside the receiver side
8285/// (for example, an argument to a call-built fluent receiver), so direct child
8286/// equality is insufficient: classify each ancestor by structured subtree
8287/// containment instead.
8288fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
8289    let candidate = node;
8290    while let Some(parent) = node.parent() {
8291        if parent.kind() == "field_expression" {
8292            if let Some(field) = parent.child_by_field_name("field")
8293                && node_is_within(field, candidate)
8294            {
8295                // The `template` disambiguator in `receiver.template f<Arg>()`
8296                // wraps the selected member in a `dependent_name` whose single
8297                // named child is the `template_*` node the plain spelling puts
8298                // directly under `field`. Unwrap it so both spellings reach the
8299                // same name/arguments split (#2196).
8300                let selected = match field.kind() {
8301                    "dependent_name" => field.named_child(0).unwrap_or(field),
8302                    _ => field,
8303                };
8304                let selected_name = match selected.kind() {
8305                    "template_method" | "template_function" | "template_type" => {
8306                        selected.child_by_field_name("name").unwrap_or(selected)
8307                    }
8308                    _ => selected,
8309                };
8310                if node_is_within(selected_name, candidate) {
8311                    return true;
8312                }
8313                // A template argument is structurally inside the field subtree,
8314                // but it is an independent reference rather than the selected
8315                // member name.
8316                node = parent;
8317                continue;
8318            }
8319            let receiver = parent
8320                .child_by_field_name("argument")
8321                .or_else(|| parent.child_by_field_name("object"))
8322                .or_else(|| parent.named_child(0));
8323            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8324                // Unknown grammar shape inside a field expression: fail closed
8325                // rather than treating it as a receiver reference.
8326                return true;
8327            }
8328        }
8329        node = parent;
8330    }
8331    false
8332}
8333
8334fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8335    let text = node_text(node, ctx.source);
8336    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8337        return false;
8338    }
8339    if text.contains("::") {
8340        return ctx.visibility.contains_named_symbol(
8341            ctx.file,
8342            text,
8343            TargetKind::GlobalField,
8344            &ctx.spec.target,
8345        );
8346    }
8347    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8348        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8349    {
8350        return ctx.visibility.contains_named_symbol(
8351            ctx.file,
8352            text,
8353            TargetKind::GlobalField,
8354            &ctx.spec.target,
8355        );
8356    }
8357    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8358        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8359            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8360                brokk_bifrost_core::analyzer::Language::Cpp,
8361                &namespace,
8362            ) == indexed_scope
8363        })
8364    {
8365        return ctx.visibility.contains_named_symbol(
8366            ctx.file,
8367            text,
8368            TargetKind::GlobalField,
8369            &ctx.spec.target,
8370        );
8371    }
8372    bare_global_field_uniquely_resolves_to_target(text, ctx)
8373}
8374
8375fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8376    let mut matched_target = false;
8377    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8378        if !has_persisted_global_field_identity(unit)
8379            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8380        {
8381            continue;
8382        }
8383        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8384            continue;
8385        }
8386        if ctx.global_field_internal_linkage_cache.read(
8387            &ctx.visibility.reads.memo_attribution(),
8388            |cells| {
8389                same_visible_global_field_symbol(
8390                    &ctx.analyzer,
8391                    &mut cells.borrow_mut(),
8392                    unit,
8393                    &ctx.spec.target,
8394                )
8395            },
8396        ) {
8397            matched_target = true;
8398        } else {
8399            return false;
8400        }
8401    }
8402    matched_target
8403}
8404
8405pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8406    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8407    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8408    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8409    // analyzer for every same-named candidate's parent.
8410    unit.is_field() && !unit.short_name().contains('.')
8411}
8412
8413fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8414    let text = node_text(node, ctx.source);
8415    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8416        return true;
8417    }
8418    if text.contains("::") {
8419        return ctx.visibility.resolve_known_non_target(
8420            ctx.file,
8421            text,
8422            TargetKind::GlobalField,
8423            &ctx.spec.target,
8424        );
8425    }
8426    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8427        return false;
8428    };
8429    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8430        && ctx
8431            .visibility
8432            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8433            .any(|unit| {
8434                has_persisted_global_field_identity(unit)
8435                    && unit.identifier() == ctx.spec.member_name
8436                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8437                    && !ctx.global_field_internal_linkage_cache.read(
8438                        &ctx.visibility.reads.memo_attribution(),
8439                        |cells| {
8440                            same_visible_global_field_symbol(
8441                                &ctx.analyzer,
8442                                &mut cells.borrow_mut(),
8443                                unit,
8444                                &ctx.spec.target,
8445                            )
8446                        },
8447                    )
8448            })
8449}
8450
8451fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8452    if is_c_offsetof_member_node(node) {
8453        maybe_record_c_offsetof_field_hit(node, ctx);
8454        return;
8455    }
8456    if node.kind() == "field_expression" {
8457        let Some(field) = node.child_by_field_name("field") else {
8458            return;
8459        };
8460        if node_text(field, ctx.source) != ctx.spec.member_name {
8461            return;
8462        }
8463        *ctx.raw_match_count += 1;
8464        if !ctx.visibility.declaration_guards_compatible_at_reference(
8465            &ctx.analyzer,
8466            ctx.file,
8467            &ctx.spec.target,
8468            field,
8469        ) {
8470            return;
8471        }
8472        let receiver = node
8473            .child_by_field_name("argument")
8474            .or_else(|| node.child_by_field_name("object"));
8475        let receiver_resolution =
8476            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8477        match receiver_resolution {
8478            Some(MethodReceiverTargetResolution::Target)
8479                if ctx.visibility.declaration_visible_at_reference(
8480                    &ctx.analyzer,
8481                    ctx.file,
8482                    &ctx.spec.target,
8483                    field,
8484                ) || (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
8485                    && ctx.spec.target.source() == ctx.file
8486                    && ctx
8487                        .visibility
8488                        .external_type_candidate_guard_compatible_in_context(
8489                            &ctx.analyzer,
8490                            ctx.file,
8491                            &ctx.spec.target,
8492                            field,
8493                        )) =>
8494            {
8495                // The receiver proves the C++ member owner, including fields
8496                // recovered without callable activation facts. Its guard still
8497                // needs implication and stability, even in the same file.
8498                push_hit(field, ctx)
8499            }
8500            Some(
8501                MethodReceiverTargetResolution::Target | MethodReceiverTargetResolution::Missing,
8502            )
8503            | None => push_unproven_hit(field, ctx),
8504            Some(
8505                MethodReceiverTargetResolution::NonTarget
8506                | MethodReceiverTargetResolution::Ambiguous,
8507            ) => {}
8508        }
8509        return;
8510    }
8511
8512    if matches!(node.kind(), "identifier" | "field_identifier")
8513        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8514        && let Some(designator_owner) =
8515            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8516    {
8517        *ctx.raw_match_count += 1;
8518        match designator_owner {
8519            DesignatedInitializerOwner::Resolved(owner)
8520                if ctx
8521                    .spec
8522                    .owner
8523                    .as_ref()
8524                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8525            {
8526                push_hit(node, ctx);
8527            }
8528            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8529            DesignatedInitializerOwner::Resolved(_) => {}
8530        }
8531        return;
8532    }
8533
8534    let qualified_member_name_matches =
8535        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8536            && cpp_name_component_nodes(node)
8537                .and_then(|components| components.last().copied())
8538                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8539    if !matches!(
8540        node.kind(),
8541        "identifier"
8542            | "field_identifier"
8543            | "qualified_identifier"
8544            | "scoped_identifier"
8545            | "type_identifier"
8546    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8547        && !qualified_member_name_matches)
8548        || !scan_leaf_is_value_position(node, ctx)
8549        || is_declaration_name(node)
8550        || is_member_field_own_declarator(node, ctx)
8551        || is_selected_field_expression_member_descendant(node)
8552        || is_nested_in_qualified_identifier(node)
8553    {
8554        return;
8555    }
8556    *ctx.raw_match_count += 1;
8557    if is_structurally_qualified(node) {
8558        match qualified_owner_resolution(node, ctx) {
8559            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8560            QualifiedOwnerResolution::NonTarget => {}
8561            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8562        }
8563        return;
8564    }
8565    let text = node_text(node, ctx.source);
8566    if ctx.local_shadows.is_shadowed(text) {
8567        return;
8568    }
8569    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8570        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8571    let owner_context = structured_owner_context_resolution(node, ctx);
8572    if matches!(
8573        owner_context,
8574        StructuredOwnerContextResolution::SelfTarget
8575            | StructuredOwnerContextResolution::InheritedTarget
8576    ) || unscoped_enum_match
8577    {
8578        push_hit(node, ctx);
8579    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8580        .then_some(ctx.spec.owner.as_ref())
8581        .flatten()
8582    {
8583        let resolution = match resolve_active_using_enum_member(node, ctx) {
8584            ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8585            ActiveUsingEnumMemberResolution::Class(resolution) => {
8586                if direct_class_member_shadows(node, ctx) {
8587                    return;
8588                }
8589                resolution
8590            }
8591            ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8592                if let Some(owner) = structured_enclosing_owner(node, ctx) {
8593                    if direct_class_member_shadows(node, ctx) {
8594                        return;
8595                    }
8596                    let complete_same_file_leaf = owner.source() == ctx.file
8597                        && ctx
8598                            .analyzer
8599                            .type_hierarchy_provider()
8600                            .is_some_and(|hierarchy| {
8601                                hierarchy
8602                                    .get_direct_ancestors(&owner)
8603                                    .definite()
8604                                    .to_vec()
8605                                    .is_empty()
8606                            });
8607                    if !complete_same_file_leaf {
8608                        push_unproven_hit(node, ctx);
8609                        return;
8610                    }
8611                }
8612                match owner_context {
8613                    StructuredOwnerContextResolution::SelfTarget
8614                    | StructuredOwnerContextResolution::InheritedTarget
8615                    | StructuredOwnerContextResolution::NonTarget => return,
8616                    StructuredOwnerContextResolution::Ambiguous => {
8617                        push_unproven_hit(node, ctx);
8618                        return;
8619                    }
8620                    StructuredOwnerContextResolution::Missing => {}
8621                }
8622                if namespace_value_shadows(node, ctx) {
8623                    return;
8624                }
8625                resolution
8626            }
8627            ActiveUsingEnumMemberResolution::Missing => {
8628                if direct_class_member_shadows(node, ctx)
8629                    || (structured_enclosing_owner(node, ctx).is_none()
8630                        && namespace_value_shadows(node, ctx))
8631                {
8632                    return;
8633                }
8634                UsingEnumMemberResolution::Missing
8635            }
8636        };
8637        match resolution {
8638            UsingEnumMemberResolution::Resolved { owner, member }
8639                if same_visible_symbol(&owner, target_owner)
8640                    && same_visible_symbol(&member, &ctx.spec.target) =>
8641            {
8642                push_hit(node, ctx);
8643            }
8644            UsingEnumMemberResolution::Resolved { .. } => {}
8645            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8646                push_unproven_hit(node, ctx)
8647            }
8648        }
8649    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8650        push_unproven_hit(node, ctx);
8651    }
8652}
8653
8654fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8655    if node_text(node, ctx.source) != ctx.spec.member_name {
8656        return;
8657    }
8658    *ctx.raw_match_count += 1;
8659    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8660        push_unproven_hit(node, ctx);
8661        return;
8662    };
8663    let owner = match resolve_type_node_lexically(
8664        type_reference,
8665        &ctx.analyzer,
8666        ctx.visibility,
8667        &ctx.ordinary_type_imports,
8668        ctx.file,
8669        ctx.source,
8670    ) {
8671        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8672        LexicalTypeResolution::Resolved { .. }
8673        | LexicalTypeResolution::Ambiguous
8674        | LexicalTypeResolution::Missing => {
8675            push_unproven_hit(member, ctx);
8676            return;
8677        }
8678    };
8679    let candidates = ctx
8680        .visibility
8681        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8682        .into_iter()
8683        .filter(|candidate| candidate.is_field())
8684        .collect::<Vec<_>>();
8685    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8686        push_hit(member, ctx);
8687    } else if candidates.len() > 1 {
8688        push_unproven_hit(member, ctx);
8689    }
8690}
8691
8692enum ActiveUsingEnumMemberResolution {
8693    Block(UsingEnumMemberResolution),
8694    Class(UsingEnumMemberResolution),
8695    Namespace(UsingEnumMemberResolution),
8696    Missing,
8697}
8698
8699fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8700    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8701        ctx.visibility
8702            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8703            .into_iter()
8704            .next()
8705            .is_some()
8706    })
8707}
8708
8709fn resolve_active_using_enum_member(
8710    node: Node<'_>,
8711    ctx: &ScanCtx<'_>,
8712) -> ActiveUsingEnumMemberResolution {
8713    let block =
8714        ctx.using_enum_owners
8715            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8716    if !matches!(block, UsingEnumMemberResolution::Missing) {
8717        return ActiveUsingEnumMemberResolution::Block(block);
8718    }
8719    let class = structured_enclosing_owner(node, ctx);
8720    let namespace = enclosing_namespace_components(node, ctx.source);
8721    match ctx.semantic_using_enum_owners.resolve_member(
8722        ctx.visibility,
8723        ctx.file,
8724        class.as_ref(),
8725        &namespace,
8726        node.start_byte(),
8727        &ctx.spec.member_name,
8728    ) {
8729        SemanticUsingEnumMemberResolution::Class(resolution) => {
8730            ActiveUsingEnumMemberResolution::Class(resolution)
8731        }
8732        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8733            ActiveUsingEnumMemberResolution::Namespace(resolution)
8734        }
8735        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8736    }
8737}
8738
8739fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8740    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8741    !matches!(
8742        resolve_namespace_value(
8743            &ctx.analyzer,
8744            ctx.visibility,
8745            ctx.file,
8746            &namespace,
8747            &ctx.spec.member_name,
8748            node.start_byte(),
8749        ),
8750        NamespaceValueResolution::Missing
8751    )
8752}
8753
8754/// Whether `node` is a component of an enclosing qualified identifier's own
8755/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8756/// reference surfaced for it.
8757///
8758/// A qualified identifier owns only that path. Everything else beneath it is an
8759/// independent reference that its own target scanner must resolve:
8760///
8761/// - `Owner::Template<argument>` holds each template argument outside the path.
8762/// - Error recovery can hang a complete member initializer off an `ERROR` child
8763///   of a synthetic qualified identifier, where no structured path exists.
8764/// - The grammar accepts a `pointer_type_declarator` as a qualified
8765///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8766///   folds a whole parameter list under one `qualified_identifier`. Parameter
8767///   default values there are ordinary value expressions (#2548).
8768///
8769/// Follow the structured `scope`/`name` links rather than every ancestor, which
8770/// covers all three cases with one rule.
8771/// Whether `node` lies on the scope/name path of an enclosing
8772/// `qualified_identifier`, which owns the complete name. The grammar nests
8773/// `::A::b` and `n::A::b` as a qualified suffix `A::b`; resolving that suffix
8774/// on its own would drop the leading `::` or `n::` qualifier.
8775fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8776    let mut current = node;
8777    while let Some(parent) = current.parent() {
8778        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8779            parent
8780                .child_by_field_name(field)
8781                .is_some_and(|component| same_node(component, current))
8782        });
8783        if !on_name_path {
8784            return false;
8785        }
8786        if parent.kind() == "qualified_identifier" {
8787            return true;
8788        }
8789        current = parent;
8790    }
8791    false
8792}
8793
8794fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8795    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8796}
8797
8798/// What a receiver spelled as a qualified name denotes.
8799///
8800/// `ns::value.run()` reads the member off the declared type of the variable
8801/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8802/// Forward navigation and the inverse walk both ask this before typing such a
8803/// receiver, so the two directions accept the same receiver spellings (#2547).
8804pub enum QualifiedReceiverBase {
8805    /// Every visible declaration of the one variable the path names.
8806    Variable(Vec<CodeUnit>),
8807    /// The type the path names when it names no variable.
8808    Type(Option<CodeUnit>),
8809    /// The path names more than one variable; typing either would be a guess.
8810    Ambiguous,
8811}
8812
8813pub fn qualified_receiver_base(
8814    visibility: &VisibilityIndex<'_>,
8815    file: &ProjectFile,
8816    receiver: Node<'_>,
8817    source: &str,
8818) -> QualifiedReceiverBase {
8819    let reference = node_text(receiver, source);
8820    let fields = visibility
8821        .named_candidates(file, reference, TargetKind::GlobalField)
8822        .into_iter()
8823        .filter(has_persisted_global_field_identity)
8824        .collect::<Vec<_>>();
8825    let Some(first) = fields.first() else {
8826        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8827    };
8828    if fields
8829        .iter()
8830        .skip(1)
8831        .any(|field| !same_visible_symbol(first, field))
8832    {
8833        return QualifiedReceiverBase::Ambiguous;
8834    }
8835    QualifiedReceiverBase::Variable(fields)
8836}
8837
8838fn receiver_type_units_with_budget(
8839    node: Node<'_>,
8840    source: &str,
8841    ctx: &ScanCtx<'_>,
8842    remaining_call_depth: usize,
8843) -> Vec<CodeUnit> {
8844    let mut current = node;
8845    let mut member_chain = Vec::new();
8846    let mut base_units = loop {
8847        match current.kind() {
8848            "field_expression" => {
8849                let Some(member) = current.child_by_field_name("field") else {
8850                    return Vec::new();
8851                };
8852                let Some(receiver) = current
8853                    .child_by_field_name("argument")
8854                    .or_else(|| current.child_by_field_name("object"))
8855                    .or_else(|| current.named_child(0))
8856                else {
8857                    return Vec::new();
8858                };
8859                member_chain.push(node_text(member, source));
8860                current = receiver;
8861            }
8862            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8863                let Some(inner) = current
8864                    .child_by_field_name("argument")
8865                    .or_else(|| current.named_child(0))
8866                else {
8867                    return Vec::new();
8868                };
8869                current = inner;
8870            }
8871            // Tree-sitter uses `field_identifier` for an unqualified member
8872            // field when it appears as the base of another field expression
8873            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8874            // same structured binding and enclosing-owner paths as an
8875            // ordinary identifier; falling through to `resolve_type` would
8876            // treat the field name as a type and lose the receiver identity.
8877            "identifier" | "field_identifier" => {
8878                let name = node_text(current, source);
8879                // Function-like macro locals are visible in the replacement
8880                // range after substitution and shadow caller bindings. The
8881                // receiver scan has no root field, so recover the source tree
8882                // root before asking the visibility index for that binding.
8883                let mut root = current;
8884                while let Some(parent) = ctx.ancestry.parent(root) {
8885                    root = parent;
8886                }
8887                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8888                    ctx.file,
8889                    root,
8890                    source,
8891                    current.start_byte(),
8892                    current.end_byte(),
8893                ) && binding.name == name
8894                {
8895                    let normalized = normalize_cpp_type_name(&binding.type_name);
8896                    let unit = binding
8897                        .proven_unit
8898                        .clone()
8899                        .or_else(|| {
8900                            binding.type_node.and_then(|type_node| {
8901                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8902                            })
8903                        })
8904                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8905                    // Keep the macro declaration's shadowing boundary even
8906                    // when its type is unavailable: an unresolved macro local
8907                    // cannot be reinterpreted as a static type or outer value.
8908                    break unit.into_iter().collect();
8909                }
8910                let local = ctx.bindings.resolve_symbol(name);
8911                if let Some(bindings) = local.as_precise() {
8912                    break receiver_units_from_bindings(current, bindings, ctx);
8913                }
8914                if ctx.bindings.is_shadowed(name) {
8915                    return Vec::new();
8916                }
8917                let owner = structured_enclosing_owner(current, ctx)
8918                    .filter(CodeUnit::is_class)
8919                    .or_else(|| {
8920                        enclosing_context(current, ctx)
8921                            .owner
8922                            .filter(CodeUnit::is_class)
8923                    });
8924                if let Some(owner) = owner {
8925                    // The enclosing class is only the search root: an implicit
8926                    // member-field receiver can be declared on any base, so the
8927                    // declaring owner comes from the same hierarchy walk the
8928                    // member chain below uses, not from an exact-parent match.
8929                    let declaring_owner = match resolve_declaring_member_owner(
8930                        &ctx.analyzer,
8931                        ctx.visibility,
8932                        ctx.file,
8933                        &owner,
8934                        name,
8935                    ) {
8936                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8937                        EnclosingMemberOwnerResolution::Missing => None,
8938                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8939                    };
8940                    if let Some(declaring_owner) = declaring_owner {
8941                        let implicit_fields = ctx
8942                            .visibility
8943                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8944                            .into_iter()
8945                            .filter(|unit| unit.is_field())
8946                            .collect::<Vec<_>>();
8947                        if !implicit_fields.is_empty() {
8948                            break receiver_units_from_declared_fields(
8949                                implicit_fields,
8950                                current,
8951                                ctx,
8952                            );
8953                        }
8954                    }
8955                }
8956                let global_fields = ctx
8957                    .visibility
8958                    .visible_identifier_candidates(ctx.file, name)
8959                    .filter(|unit| {
8960                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8961                    })
8962                    .collect::<Vec<_>>();
8963                if global_fields.is_empty() {
8964                    break ctx
8965                        .visibility
8966                        .resolve_type(ctx.file, name)
8967                        .into_iter()
8968                        .collect();
8969                }
8970                if let Some(first) = global_fields.first()
8971                    && global_fields.iter().skip(1).any(|field| {
8972                        !ctx.global_field_internal_linkage_cache.read(
8973                            &ctx.visibility.reads.memo_attribution(),
8974                            |cells| {
8975                                same_visible_global_field_symbol(
8976                                    &ctx.analyzer,
8977                                    &mut cells.borrow_mut(),
8978                                    first,
8979                                    field,
8980                                )
8981                            },
8982                        )
8983                    })
8984                {
8985                    return Vec::new();
8986                }
8987                break receiver_units_from_declared_fields(global_fields, current, ctx);
8988            }
8989            "call_expression" | "new_expression" => {
8990                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8991                    .and_then(|binding| binding.unit)
8992                    .into_iter()
8993                    .collect();
8994            }
8995            // `((T*)p)->member` is typed by what the cast spells, not by the
8996            // operand it converts (#2981). Pointer depth does not change which
8997            // type declares the member, so only the descriptor's type matters.
8998            "cast_expression" => {
8999                let Some(descriptor) = current.child_by_field_name("type") else {
9000                    return Vec::new();
9001                };
9002                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
9003                    return Vec::new();
9004                };
9005                break unit.into_iter().collect();
9006            }
9007            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
9008                let name = node_text(current, source);
9009                let local = ctx.bindings.resolve_symbol(name);
9010                if let Some(bindings) = local.as_precise() {
9011                    break receiver_units_from_bindings(current, bindings, ctx);
9012                }
9013                return Vec::new();
9014            }
9015            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
9016            "qualified_identifier" | "scoped_identifier" => {
9017                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
9018                    QualifiedReceiverBase::Variable(fields) => {
9019                        break receiver_units_from_declared_fields(
9020                            fields.iter().collect(),
9021                            current,
9022                            ctx,
9023                        );
9024                    }
9025                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
9026                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
9027                }
9028            }
9029            _ => {
9030                break ctx
9031                    .visibility
9032                    .resolve_type(ctx.file, node_text(current, source))
9033                    .into_iter()
9034                    .collect();
9035            }
9036        }
9037    };
9038
9039    base_units = canonical_receiver_units(base_units, ctx);
9040    if base_units.is_empty() {
9041        return Vec::new();
9042    }
9043
9044    while let Some(member_name) = member_chain.pop() {
9045        let mut next_units = Vec::new();
9046        for owner in &base_units {
9047            let declaring_owner = match resolve_declaring_member_owner(
9048                &ctx.analyzer,
9049                ctx.visibility,
9050                ctx.file,
9051                owner,
9052                member_name,
9053            ) {
9054                EnclosingMemberOwnerResolution::Owner(owner) => owner,
9055                EnclosingMemberOwnerResolution::Missing => continue,
9056                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
9057            };
9058            let fields = ctx.visibility.visible_members_for_owner_name(
9059                ctx.file,
9060                &declaring_owner,
9061                member_name,
9062            );
9063            for field in fields.into_iter().filter(|unit| unit.is_field()) {
9064                let Some(unit) =
9065                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9066                        .and_then(|binding| binding.unit)
9067                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
9068                else {
9069                    continue;
9070                };
9071                if !next_units
9072                    .iter()
9073                    .any(|existing| same_visible_symbol(existing, &unit))
9074                {
9075                    next_units.push(unit);
9076                }
9077            }
9078        }
9079        if next_units.is_empty() {
9080            return Vec::new();
9081        }
9082        base_units = unanimous_receiver_units(next_units);
9083        if base_units.is_empty() {
9084            return Vec::new();
9085        }
9086    }
9087    base_units
9088}
9089
9090fn receiver_units_from_bindings(
9091    node: Node<'_>,
9092    bindings: &HashSet<CppScanBinding>,
9093    ctx: &ScanCtx<'_>,
9094) -> Vec<CodeUnit> {
9095    let mut units = Vec::new();
9096    for binding in bindings {
9097        let raw_unit = if let Some(unit) = &binding.unit {
9098            unit.clone()
9099        } else {
9100            let Some(type_name) = binding.type_name.as_deref() else {
9101                return Vec::new();
9102            };
9103            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
9104                return Vec::new();
9105            };
9106            unit
9107        };
9108        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
9109            // Type aliases are represented as class units. When an alias has
9110            // a dependent target, canonicalization deliberately preserves
9111            // that alias rather than inventing a concrete class. Give the
9112            // existing target-guided structured recovery a chance to prove
9113            // the queried owner before accepting that unresolved identity.
9114            if same_visible_symbol(&unit, &raw_unit)
9115                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
9116            {
9117                units.push(recovered);
9118                continue;
9119            }
9120            units.push(unit);
9121            continue;
9122        }
9123        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
9124            units.push(unit);
9125            continue;
9126        }
9127        return Vec::new();
9128    }
9129    unanimous_receiver_units(units)
9130}
9131
9132/// Resolve a using-alias receiver from its declaration's structured RHS when
9133/// the alias target index cannot cross a malformed namespace-sentinel node.
9134/// The inverse target owner supplies only the exact class identity to prove;
9135/// lexical AST resolution still decides whether the alias denotes that class.
9136fn recovered_receiver_alias_target(
9137    reference: Node<'_>,
9138    alias: &CodeUnit,
9139    ctx: &ScanCtx<'_>,
9140) -> Option<CodeUnit> {
9141    if !ctx
9142        .analyzer
9143        .type_alias_provider()
9144        .is_some_and(|provider| provider.is_type_alias(alias))
9145    {
9146        return None;
9147    }
9148    let target = ctx.spec.owner.as_ref()?.clone();
9149    if !target.is_class() || alias.source() != ctx.file {
9150        return None;
9151    }
9152    let range = ctx
9153        .analyzer
9154        .ranges(alias)
9155        .into_iter()
9156        .find(|range| range.start_byte < range.end_byte)?;
9157    let mut node =
9158        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9159    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
9160        node = ctx.ancestry.parent(node)?;
9161    }
9162    let type_descriptor = node.child_by_field_name("type")?;
9163    let type_node = receiver_type_node_base(type_descriptor);
9164    let resolution = resolve_type_node_lexically_for_target(
9165        type_node,
9166        &ctx.analyzer,
9167        ctx.visibility,
9168        &ctx.ordinary_type_imports,
9169        ctx.file,
9170        ctx.source,
9171        &target,
9172        Some(&ctx.lexical_scope_cache),
9173        ctx.recovered_sentinel_scope(type_node).as_deref(),
9174    );
9175    if let LexicalTypeResolution::Resolved {
9176        unit, candidates, ..
9177    } = resolution
9178        && (same_visible_symbol(&unit, &target)
9179            || candidates
9180                .iter()
9181                .any(|candidate| same_visible_symbol(candidate, &target)))
9182    {
9183        return Some(target);
9184    }
9185    let (components, global) = type_reference_components(type_node, ctx.source)?;
9186    if !global
9187        && components.len() == 2
9188        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
9189        && dependent_nested_alias_concrete_target(
9190            &ctx.analyzer,
9191            ctx.visibility,
9192            ctx.file,
9193            alias,
9194            &components[1],
9195            type_node,
9196        )
9197        .is_some_and(|unit| same_visible_symbol(&unit, &target))
9198    {
9199        return Some(target);
9200    }
9201    let scope = ctx
9202        .recovered_sentinel_scope(type_node)
9203        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9204    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
9205    let visible = ctx.visibility.external_type_candidate_visible_in_context(
9206        &ctx.analyzer,
9207        ctx.file,
9208        &target,
9209        type_node,
9210    );
9211    (path_matches && visible).then_some(target)
9212}
9213
9214/// The one concrete class that `alias`, spelled `Param::nested_name` through an
9215/// active template type parameter `Param`, can name: every other visible
9216/// class-owned alias named `nested_name` must reach the same indexed class
9217/// (#2250). `type_node` is the alias's target type node, whose guard context
9218/// each candidate must be compatible with. Competing concrete targets answer
9219/// `None`.
9220pub fn dependent_nested_alias_concrete_target(
9221    analyzer: &CppGraphSource<'_>,
9222    visibility: &VisibilityIndex<'_>,
9223    file: &ProjectFile,
9224    alias: &CodeUnit,
9225    nested_name: &str,
9226    type_node: Node<'_>,
9227) -> Option<CodeUnit> {
9228    let alias_provider = analyzer.type_alias_provider()?;
9229    let concrete = visibility
9230        .visible_identifier_candidates(file, nested_name)
9231        .filter(|candidate| {
9232            alias_provider.is_type_alias(candidate)
9233                && !same_visible_symbol(candidate, alias)
9234                && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
9235                && visibility.is_physically_visible(file, candidate)
9236                && visibility.external_type_candidate_guard_compatible_in_context(
9237                    analyzer, file, candidate, type_node,
9238                )
9239        })
9240        .filter_map(|candidate| {
9241            let canonical =
9242                visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
9243            // Another dependent alias can have the same nested name but
9244            // still canonicalize only to itself. It supplies no concrete
9245            // receiver identity and therefore cannot compete with an
9246            // alias that reaches an indexed class.
9247            (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
9248        })
9249        .collect::<Vec<_>>();
9250    match unanimous_receiver_units(concrete).as_slice() {
9251        [unit] => Some(unit.clone()),
9252        _ => None,
9253    }
9254}
9255
9256fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9257    let normalized = normalize_cpp_type_name(type_name);
9258    if normalized.is_empty() {
9259        return None;
9260    }
9261
9262    // A function-local alias is intentionally absent from the visibility
9263    // index. Recover its RHS from the structured alias declaration before
9264    // trying file-visible type lookup; this keeps the alias's lexical shadow
9265    // boundary intact.
9266    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
9267        match resolve_receiver_type_node(alias_type, ctx) {
9268            Ok(Some(unit)) => return Some(unit),
9269            Err(_) => return None,
9270            Ok(None) => {}
9271        }
9272    }
9273
9274    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
9275        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
9276        LexicalTypeResolution::Ambiguous => return None,
9277        LexicalTypeResolution::Missing => {}
9278    }
9279    let candidates = ctx
9280        .visibility
9281        .type_name_candidates(ctx.file, &normalized)
9282        .into_iter()
9283        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
9284        .collect();
9285    unanimous_receiver_units(candidates).into_iter().next()
9286}
9287
9288/// The receiver type a type node names: the visible indexed type it resolves
9289/// to, else the type its components name in the enclosing lexical scope.
9290///
9291/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
9292/// is unwrapped to its type specifier first, so every caller that has a spelled
9293/// type reads it the same way. `Err` reports a template whose arguments could
9294/// not be resolved; that is a structured failure, not an absent type.
9295fn resolve_receiver_type_node(
9296    type_node: Node<'_>,
9297    ctx: &ScanCtx<'_>,
9298) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
9299    let type_node = receiver_type_node_base(type_node);
9300    if let Some(local) = function_local_type_for_node(type_node, ctx) {
9301        return Ok(local);
9302    }
9303    if let Some(unit) = ctx
9304        .visibility
9305        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
9306    {
9307        return Ok(Some(unit));
9308    }
9309    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
9310}
9311
9312fn resolve_receiver_type_node_lexically(
9313    type_node: Node<'_>,
9314    ctx: &ScanCtx<'_>,
9315) -> Option<CodeUnit> {
9316    let type_node = receiver_type_node_base(type_node);
9317    let components = cpp_type_name_components(type_node, ctx.source)?;
9318    let lexical_scope = match enclosing_lexical_scope_components(
9319        type_node,
9320        &ctx.analyzer,
9321        ctx.visibility,
9322        ctx.file,
9323        ctx.source,
9324    ) {
9325        LexicalScopeResolution::Resolved(scope) => scope,
9326        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
9327    };
9328    match ctx.visibility.resolve_type_components_lexically(
9329        &ctx.analyzer,
9330        ctx.file,
9331        &components,
9332        is_globally_qualified_cpp_name(type_node),
9333        &lexical_scope,
9334    ) {
9335        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
9336        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9337    }
9338}
9339
9340fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
9341    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
9342        let Some(inner) = node.child_by_field_name("type").or_else(|| {
9343            if node.kind() == "dependent_type" {
9344                node.named_child(0)
9345            } else {
9346                None
9347            }
9348        }) else {
9349            break;
9350        };
9351        node = inner;
9352    }
9353    node
9354}
9355
9356fn resolve_receiver_type_name_lexically(
9357    node: Node<'_>,
9358    normalized: &str,
9359    ctx: &ScanCtx<'_>,
9360) -> LexicalTypeResolution {
9361    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9362        brokk_bifrost_core::analyzer::Language::Cpp,
9363        normalized,
9364    );
9365    if components.is_empty() {
9366        return LexicalTypeResolution::Missing;
9367    }
9368    let lexical_scope = match enclosing_lexical_scope_components(
9369        node,
9370        &ctx.analyzer,
9371        ctx.visibility,
9372        ctx.file,
9373        ctx.source,
9374    ) {
9375        LexicalScopeResolution::Resolved(scope) => scope,
9376        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9377        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9378    };
9379    ctx.visibility.resolve_type_components_lexically(
9380        &ctx.analyzer,
9381        ctx.file,
9382        &components,
9383        normalized.starts_with("::"),
9384        &lexical_scope,
9385    )
9386}
9387
9388fn local_receiver_alias_type_node<'tree>(
9389    node: Node<'tree>,
9390    name: &str,
9391    ctx: &ScanCtx<'_>,
9392) -> Option<Node<'tree>> {
9393    let callable = nearest_callable_scope(node)?;
9394    let mut root_callable = callable;
9395    // See `target_guided_nested_type_terminal_hit`: the result carries the
9396    // caller's tree lifetime.
9397    let mut ancestor = callable.parent();
9398    while let Some(current) = ancestor {
9399        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9400            root_callable = current;
9401        }
9402        ancestor = current.parent();
9403    }
9404
9405    let mut stack = vec![root_callable];
9406    let mut best = None;
9407    while let Some(current) = stack.pop() {
9408        if current.start_byte() >= node.start_byte() {
9409            continue;
9410        }
9411        if local_type_alias_name_node(current)
9412            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9413            && current
9414                .parent()
9415                .is_none_or(|parent| parent.kind() != "template_declaration")
9416            && has_function_scope_ancestor(current)
9417            && local_alias_scope_contains_node(current, node)
9418        {
9419            let replace = best
9420                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9421            if replace {
9422                best = current.child_by_field_name("type");
9423            }
9424        }
9425        let mut cursor = current.walk();
9426        stack.extend(current.named_children(&mut cursor));
9427    }
9428    best
9429}
9430
9431fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9432    let mut canonical = Vec::with_capacity(units.len());
9433    for unit in units {
9434        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9435            return Vec::new();
9436        };
9437        canonical.push(unit);
9438    }
9439    unanimous_receiver_units(canonical)
9440}
9441
9442fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9443    if let Some(cached) = ctx
9444        .receiver_canonical_type_cache
9445        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
9446            cells.borrow().get(unit).cloned()
9447        })
9448    {
9449        return cached;
9450    }
9451    let canonical = ctx
9452        .visibility
9453        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9454    ctx.receiver_canonical_type_cache
9455        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
9456            cells.borrow_mut().insert(unit.clone(), canonical.clone())
9457        });
9458    canonical
9459}
9460
9461fn receiver_units_from_declared_fields(
9462    fields: Vec<&CodeUnit>,
9463    reference: Node<'_>,
9464    ctx: &ScanCtx<'_>,
9465) -> Vec<CodeUnit> {
9466    let Some(first) = fields.first() else {
9467        return Vec::new();
9468    };
9469    if fields
9470        .iter()
9471        .skip(1)
9472        .any(|field| !same_visible_symbol(first, field))
9473    {
9474        return Vec::new();
9475    }
9476    unanimous_receiver_units(
9477        fields
9478            .into_iter()
9479            .filter_map(|field| {
9480                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9481                    .and_then(|binding| binding.unit)
9482                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9483            })
9484            .collect(),
9485    )
9486}
9487
9488/// Resolve a field receiver's declared type from its structured declaration
9489/// when the persisted type fact was built under a malformed sentinel scope.
9490/// The queried member owner supplies the exact class identity to prove; the
9491/// declaration's type node and recovered lexical path provide the evidence.
9492fn recovered_receiver_field_type(
9493    reference: Node<'_>,
9494    field: &CodeUnit,
9495    ctx: &ScanCtx<'_>,
9496) -> Option<CodeUnit> {
9497    let target = ctx.spec.owner.as_ref()?.clone();
9498    if !target.is_class() || field.source() != ctx.file {
9499        return None;
9500    }
9501    let range = ctx
9502        .analyzer
9503        .ranges(field)
9504        .into_iter()
9505        .find(|range| range.start_byte < range.end_byte)?;
9506    let mut declaration =
9507        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9508    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9509        declaration = ctx.ancestry.parent(declaration)?;
9510    }
9511    let type_node = first_type_child(declaration)?;
9512    let resolution = resolve_type_node_lexically_for_target(
9513        type_node,
9514        &ctx.analyzer,
9515        ctx.visibility,
9516        &ctx.ordinary_type_imports,
9517        ctx.file,
9518        ctx.source,
9519        &target,
9520        Some(&ctx.lexical_scope_cache),
9521        ctx.recovered_sentinel_scope(type_node).as_deref(),
9522    );
9523    if let LexicalTypeResolution::Resolved {
9524        unit, candidates, ..
9525    } = resolution
9526        && (same_visible_symbol(&unit, &target)
9527            || candidates
9528                .iter()
9529                .any(|candidate| same_visible_symbol(candidate, &target)))
9530    {
9531        return Some(target);
9532    }
9533    let type_node = receiver_type_node_base(type_node);
9534    let (components, global) = type_reference_components(type_node, ctx.source)?;
9535    let scope = ctx
9536        .recovered_sentinel_scope(type_node)
9537        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9538    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9539        && ctx.visibility.external_type_candidate_visible_in_context(
9540            &ctx.analyzer,
9541            ctx.file,
9542            &target,
9543            type_node,
9544        ))
9545    .then_some(target)
9546}
9547
9548fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9549    let mut unique = Vec::new();
9550    for unit in units {
9551        if !unique
9552            .iter()
9553            .any(|existing| same_visible_symbol(existing, &unit))
9554        {
9555            unique.push(unit);
9556            if unique.len() > 1 {
9557                return Vec::new();
9558            }
9559        }
9560    }
9561    unique
9562}
9563
9564fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9565    let Some(owner) = ctx.spec.owner.as_ref() else {
9566        return false;
9567    };
9568    match node.kind() {
9569        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9570        // When that structured lookup fails, the base expression's owner is not the field's
9571        // type: treating `value_` as `Value` would misclassify
9572        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9573        // with the `this` node itself, so failing closed for an unresolved field chain does not
9574        // discard genuine self calls.
9575        "field_expression" => false,
9576        "call_expression" => node
9577            .child_by_field_name("function")
9578            .is_some_and(|function| receiver_matches_target(function, ctx)),
9579        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9580            .child_by_field_name("argument")
9581            .or_else(|| node.named_child(0))
9582            .is_some_and(|child| receiver_matches_target(child, ctx)),
9583        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9584            .bindings
9585            .resolve_symbol(node_text(node, ctx.source))
9586            .as_precise()
9587            .is_some_and(|targets| {
9588                targets
9589                    .iter()
9590                    .filter_map(|target| target.unit.as_ref())
9591                    .any(|target| {
9592                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9593                    })
9594            }),
9595        "this" => same_owner_context(node, ctx),
9596        _ => qualified_owner_matches(node, ctx),
9597    }
9598}
9599
9600fn declaring_owner_for_explicit_receiver(
9601    receiver: Node<'_>,
9602    call_arity: Option<usize>,
9603    ctx: &ScanCtx<'_>,
9604) -> EnclosingMemberOwnerResolution {
9605    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9606        return EnclosingMemberOwnerResolution::Missing;
9607    }
9608    declaring_owner_from_receiver_units(
9609        receiver_type_units(receiver, ctx.source, ctx),
9610        receiver.start_byte(),
9611        call_arity,
9612        ctx,
9613    )
9614}
9615
9616/// The owner that declares the queried member for a receiver already typed.
9617///
9618/// `reference_byte` is the reference's position in the scanned file, which
9619/// decides which declarations are visible there. A receiver recovered from a
9620/// macro replacement is typed against the sentinel parse but still reported at
9621/// its bytes in the defining file, so the two arrive separately.
9622fn declaring_owner_from_receiver_units(
9623    receiver_units: Vec<CodeUnit>,
9624    reference_byte: usize,
9625    call_arity: Option<usize>,
9626    ctx: &ScanCtx<'_>,
9627) -> EnclosingMemberOwnerResolution {
9628    let mut declaring_owner = None;
9629    for receiver_owner in receiver_units {
9630        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9631            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9632        }) {
9633            if declaring_owner
9634                .as_ref()
9635                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9636            {
9637                return EnclosingMemberOwnerResolution::Ambiguous;
9638            }
9639            declaring_owner = Some(receiver_owner);
9640            continue;
9641        }
9642        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9643        let owner_resolution = match call_arity {
9644            Some(arity) => resolve_declaring_callable_owner(
9645                &ctx.analyzer,
9646                ctx.visibility,
9647                ctx.file,
9648                ordinary,
9649                &ctx.spec.member_name,
9650                arity,
9651            ),
9652            None => ordinary,
9653        };
9654        match owner_resolution {
9655            EnclosingMemberOwnerResolution::Owner(owner) => {
9656                if declaring_owner
9657                    .as_ref()
9658                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9659                {
9660                    return EnclosingMemberOwnerResolution::Ambiguous;
9661                }
9662                declaring_owner = Some(owner);
9663            }
9664            EnclosingMemberOwnerResolution::Ambiguous => {
9665                return EnclosingMemberOwnerResolution::Ambiguous;
9666            }
9667            EnclosingMemberOwnerResolution::Missing => {}
9668        }
9669    }
9670    declaring_owner
9671        .map(EnclosingMemberOwnerResolution::Owner)
9672        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9673}
9674
9675fn declaring_owner_from_call_function(
9676    function: Node<'_>,
9677    call_arity: Option<usize>,
9678    ctx: &ScanCtx<'_>,
9679) -> Option<EnclosingMemberOwnerResolution> {
9680    match function.kind() {
9681        "field_expression" => function
9682            .child_by_field_name("argument")
9683            .or_else(|| function.child_by_field_name("object"))
9684            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9685            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9686        "call_expression" => function
9687            .child_by_field_name("function")
9688            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9689        _ => None,
9690    }
9691}
9692
9693enum MethodReceiverTargetResolution {
9694    Target,
9695    NonTarget,
9696    Ambiguous,
9697    Missing,
9698}
9699
9700fn method_receiver_target_resolution(
9701    node: Node<'_>,
9702    declaring_owner: EnclosingMemberOwnerResolution,
9703    ctx: &ScanCtx<'_>,
9704) -> MethodReceiverTargetResolution {
9705    match declaring_owner {
9706        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9707            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9708        }
9709        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9710            MethodReceiverTargetResolution::Missing
9711        }
9712        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9713            MethodReceiverTargetResolution::Target
9714        }
9715        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9716            MethodReceiverTargetResolution::NonTarget
9717        }
9718        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9719    }
9720}
9721
9722/// Decide the queried member against a receiver's declaring owner alone.
9723///
9724/// This is the part of receiver typing that needs nothing but the owner and
9725/// the reference's position, so a member call recovered from a macro
9726/// replacement reaches the same verdict as ordinary code even though its
9727/// syntax lives in a separate sentinel parse.
9728fn declaring_owner_target_resolution(
9729    declaring_owner: EnclosingMemberOwnerResolution,
9730    reference_byte: usize,
9731    ctx: &ScanCtx<'_>,
9732) -> MethodReceiverTargetResolution {
9733    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9734        return MethodReceiverTargetResolution::Missing;
9735    };
9736    match declaring_owner {
9737        EnclosingMemberOwnerResolution::Owner(owner)
9738            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9739        {
9740            MethodReceiverTargetResolution::Target
9741        }
9742        EnclosingMemberOwnerResolution::Owner(owner)
9743            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9744        {
9745            MethodReceiverTargetResolution::NonTarget
9746        }
9747        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9748            MethodReceiverTargetResolution::Missing
9749        }
9750        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9751    }
9752}
9753
9754fn explicit_receiver_target_resolution(
9755    receiver: Node<'_>,
9756    call_arity: Option<usize>,
9757    ctx: &ScanCtx<'_>,
9758) -> MethodReceiverTargetResolution {
9759    method_receiver_target_resolution(
9760        receiver,
9761        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9762        ctx,
9763    )
9764}
9765
9766fn call_function_target_resolution(
9767    function: Node<'_>,
9768    ctx: &ScanCtx<'_>,
9769) -> MethodReceiverTargetResolution {
9770    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9771        (call.kind() == "call_expression")
9772            .then(|| {
9773                ctx.visibility
9774                    .call_arity_evidence(ctx.file, call, ctx.source)
9775                    .exact()
9776            })
9777            .flatten()
9778    });
9779    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9780    else {
9781        // A bare function identifier has an implicit receiver. Do not reinterpret
9782        // that identifier as a same-named type or value before enclosing-owner
9783        // lookup gets a chance to establish the member call.
9784        return MethodReceiverTargetResolution::Missing;
9785    };
9786    method_receiver_target_resolution(function, declaring_owner, ctx)
9787}
9788
9789fn receiver_owner_matches_target(
9790    receiver_owner: &CodeUnit,
9791    target_owner: &CodeUnit,
9792    reference_byte: usize,
9793    ctx: &ScanCtx<'_>,
9794) -> bool {
9795    same_symbol(receiver_owner, target_owner)
9796        || same_logical_symbol(receiver_owner, target_owner)
9797            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9798                || (ctx.spec.owner_is_forward_declaration
9799                    && ctx
9800                        .visibility
9801                        .is_physically_visible(ctx.file, receiver_owner))
9802                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9803                || target_group_contains_owner_peer(receiver_owner, ctx)
9804                // An identical definition of the class in a header this file
9805                // includes is the target's owner too, the relation forward
9806                // definition lookup crosses from the body's side (#6902).
9807                || ctx.visibility.same_logical_class_definition(
9808                    &ctx.analyzer,
9809                    receiver_owner,
9810                    target_owner,
9811                ))
9812}
9813
9814fn receiver_owner_is_known_non_target(
9815    receiver_owner: &CodeUnit,
9816    target_owner: &CodeUnit,
9817    reference_byte: usize,
9818    ctx: &ScanCtx<'_>,
9819) -> bool {
9820    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9821        return false;
9822    }
9823    if !same_logical_symbol(receiver_owner, target_owner) {
9824        return true;
9825    }
9826    !ctx.target_group.iter().any(|target| {
9827        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9828    })
9829}
9830
9831fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9832    ctx.visibility
9833        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9834        && ctx.target_group.iter().any(|target| {
9835            type_owner_of(&ctx.analyzer, target)
9836                .as_ref()
9837                .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9838        })
9839}
9840
9841fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9842    same_symbol(candidate_owner, owner)
9843        || (same_logical_symbol(candidate_owner, owner)
9844            && candidate_owner.source() == owner.source())
9845}
9846
9847fn visible_target_peer_matches_owner(
9848    owner: &CodeUnit,
9849    reference_byte: usize,
9850    ctx: &ScanCtx<'_>,
9851) -> bool {
9852    ctx.visibility
9853        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9854        && ctx
9855            .visibility
9856            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9857            .any(|candidate| {
9858                cpp_callable_definitions_share_identity_evidence(
9859                    &ctx.analyzer,
9860                    candidate,
9861                    &ctx.spec.target,
9862                ) && ctx.visibility.declaration_visible_at(
9863                    &ctx.analyzer,
9864                    ctx.file,
9865                    candidate,
9866                    reference_byte,
9867                ) && type_owner_of(&ctx.analyzer, candidate)
9868                    .as_ref()
9869                    .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9870            })
9871}
9872
9873fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9874    match function.kind() {
9875        "field_expression" => function
9876            .child_by_field_name("argument")
9877            .or_else(|| function.child_by_field_name("object"))
9878            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9879        _ => receiver_is_self_like(function, reference_is_c),
9880    }
9881}
9882
9883fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9884    let Some(owner) = ctx.spec.owner.as_ref() else {
9885        return false;
9886    };
9887    match node.kind() {
9888        "field_expression" => node
9889            .child_by_field_name("argument")
9890            .or_else(|| node.child_by_field_name("object"))
9891            .is_some_and(|receiver| {
9892                // `this` names the enclosing class, not necessarily the class that
9893                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9894                // lookup decide whether this is a same-owner call, an inherited base
9895                // member, a nearer override, or an ambiguous base. Rejecting a derived
9896                // `this` merely because its immediate type differs from the target owner
9897                // drops genuine inherited calls (#2541).
9898                if receiver_is_self_like(
9899                    receiver,
9900                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9901                ) {
9902                    return false;
9903                }
9904                if receiver_is_known_function_declarator(receiver, ctx) {
9905                    return true;
9906                }
9907                let units = receiver_type_units(receiver, ctx.source, ctx);
9908                !units.is_empty()
9909                    && units.iter().all(|target| {
9910                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9911                    })
9912            }),
9913        "call_expression" => node
9914            .child_by_field_name("function")
9915            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9916        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9917            .child_by_field_name("argument")
9918            .or_else(|| node.named_child(0))
9919            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9920        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9921            .bindings
9922            .resolve_symbol(node_text(node, ctx.source))
9923            .as_precise()
9924            .is_some_and(|targets| {
9925                let units = targets
9926                    .iter()
9927                    .filter_map(|target| target.unit.as_ref())
9928                    .collect::<Vec<_>>();
9929                !units.is_empty()
9930                    && units.iter().all(|target| {
9931                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9932                    })
9933            }),
9934        "this" => known_non_target_owner_context(node, ctx),
9935        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9936            qualified_owner_is_known_non_target(node, ctx)
9937        }
9938        _ => false,
9939    }
9940}
9941
9942fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9943    if receiver.kind() != "identifier" {
9944        return false;
9945    }
9946    let receiver_name = node_text(receiver, ctx.source);
9947    let mut current = receiver;
9948    while let Some(parent) = ctx.ancestry.parent(current) {
9949        if parent.kind() == "compound_statement" {
9950            let mut cursor = parent.walk();
9951            for statement in parent.named_children(&mut cursor) {
9952                if statement.start_byte() >= receiver.start_byte() {
9953                    break;
9954                }
9955                if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9956                {
9957                    continue;
9958                }
9959                let type_text = statement
9960                    .child_by_field_name("type")
9961                    .or_else(|| first_type_child(statement))
9962                    .map(|type_node| node_text(type_node, ctx.source));
9963                let mut declaration_cursor = statement.walk();
9964                for child in statement.named_children(&mut declaration_cursor) {
9965                    let declarator = if child.kind() == "init_declarator" {
9966                        child.child_by_field_name("declarator")
9967                    } else if is_declarator_node(child) {
9968                        Some(child)
9969                    } else {
9970                        None
9971                    };
9972                    let Some(declarator) = declarator else {
9973                        continue;
9974                    };
9975                    if declarator.kind() == "function_declarator"
9976                        && extract_variable_name(declarator, ctx.source)
9977                            .is_some_and(|name| name == receiver_name)
9978                        && function_declarator_parameters_have_known_types(declarator, ctx)
9979                        && !constructor_style_local_declaration(
9980                            ctx.visibility,
9981                            ctx.file,
9982                            ctx.source,
9983                            declarator,
9984                            type_text,
9985                            &ctx.bindings,
9986                        )
9987                    {
9988                        return true;
9989                    }
9990                }
9991            }
9992            return false;
9993        }
9994        current = parent;
9995    }
9996    false
9997}
9998
9999fn function_declarator_parameters_have_known_types(
10000    declarator: Node<'_>,
10001    ctx: &ScanCtx<'_>,
10002) -> bool {
10003    let Some(parameters) = declarator.child_by_field_name("parameters") else {
10004        return false;
10005    };
10006    let mut cursor = parameters.walk();
10007    parameters.named_children(&mut cursor).all(|parameter| {
10008        if !matches!(
10009            parameter.kind(),
10010            "parameter_declaration" | "optional_parameter_declaration"
10011        ) {
10012            return false;
10013        }
10014        let Some(type_node) = parameter
10015            .child_by_field_name("type")
10016            .or_else(|| first_type_child(parameter))
10017        else {
10018            return false;
10019        };
10020        type_node.kind() == "primitive_type"
10021            || ctx
10022                .visibility
10023                .resolve_type_node_result(ctx.file, type_node, ctx.source)
10024                .ok()
10025                .flatten()
10026                .is_some()
10027    })
10028}
10029
10030#[derive(Clone, Copy, PartialEq, Eq)]
10031enum QualifiedOwnerResolution {
10032    Target,
10033    NonTarget,
10034    Unresolved,
10035}
10036
10037#[derive(Clone)]
10038pub enum LexicalScopeResolution {
10039    Resolved(Vec<String>),
10040    Ambiguous,
10041    Missing,
10042}
10043
10044/// One scan's memo of lexical scope reconstructions for one file, keyed by
10045/// the reconstruction's anchor range and flags, with the file's recovered
10046/// namespace regions that the key and the reconstruction both read.
10047type LexicalScopeResolutions = HashMap<(usize, usize, bool, bool), LexicalScopeResolution>;
10048
10049pub struct LexicalScopeCache {
10050    resolutions: CaptureScopedMemo<RefCell<LexicalScopeResolutions>>,
10051    orphaned: Arc<OrphanedNamespaceScopeIndex>,
10052}
10053
10054impl LexicalScopeCache {
10055    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
10056        Self {
10057            resolutions: CaptureScopedMemo::new(RefCell::new(HashMap::default())),
10058            orphaned: orphaned_namespace_scopes(visibility, file),
10059        }
10060    }
10061}
10062
10063/// The namespaces parse recovery dropped from `file`, memoized on the
10064/// analyzer (issue #1537).
10065fn orphaned_namespace_scopes(
10066    visibility: &VisibilityIndex<'_>,
10067    file: &ProjectFile,
10068) -> Arc<OrphanedNamespaceScopeIndex> {
10069    visibility
10070        .cpp()
10071        .orphaned_namespace_scopes(visibility.token(), file)
10072}
10073
10074fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10075    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
10076}
10077
10078fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10079    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
10080}
10081
10082fn is_structurally_qualified(node: Node<'_>) -> bool {
10083    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10084        && qualified_name_has_concrete_scope_separators(node)
10085}
10086
10087fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
10088    let Some(target_owner) = ctx.spec.owner.as_ref() else {
10089        return QualifiedOwnerResolution::Unresolved;
10090    };
10091    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
10092        return QualifiedOwnerResolution::Unresolved;
10093    };
10094    // A malformed wrapper can make the parser-derived enclosing owner look
10095    // like the lexical namespace (for example tinyxml2's macro-prefixed
10096    // XMLHandle declarations). Recover the target namespace only while this
10097    // function owns scope reconstruction. An explicit recovered scope is
10098    // authoritative and enters the scoped resolver directly.
10099    if !global
10100        && !matches!(
10101            enclosing_lexical_scope_components(
10102                node,
10103                &ctx.analyzer,
10104                ctx.visibility,
10105                ctx.file,
10106                ctx.source,
10107            ),
10108            LexicalScopeResolution::Resolved(_)
10109        )
10110    {
10111        return QualifiedOwnerResolution::Unresolved;
10112    }
10113    match resolve_type_components_lexically_at_for_target_with_scope_cache(
10114        node,
10115        &components,
10116        global,
10117        &ctx.analyzer,
10118        ctx.visibility,
10119        &ctx.ordinary_type_imports,
10120        ctx.file,
10121        ctx.source,
10122        target_owner,
10123        false,
10124        Some(&ctx.lexical_scope_cache),
10125    ) {
10126        LexicalTypeResolution::Resolved { unit: owner, .. } => {
10127            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
10128                return QualifiedOwnerResolution::Target;
10129            }
10130            match cached_declaring_member_owner(&owner, ctx) {
10131                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10132                    if receiver_owner_matches_target(
10133                        &declaring_owner,
10134                        target_owner,
10135                        node.start_byte(),
10136                        ctx,
10137                    ) =>
10138                {
10139                    QualifiedOwnerResolution::Target
10140                }
10141                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10142                    if receiver_owner_is_known_non_target(
10143                        &declaring_owner,
10144                        target_owner,
10145                        node.start_byte(),
10146                        ctx,
10147                    ) =>
10148                {
10149                    QualifiedOwnerResolution::NonTarget
10150                }
10151                EnclosingMemberOwnerResolution::Owner(_)
10152                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
10153                EnclosingMemberOwnerResolution::Missing
10154                    if same_visible_symbol(&owner, target_owner) =>
10155                {
10156                    QualifiedOwnerResolution::Unresolved
10157                }
10158                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
10159            }
10160        }
10161        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
10162            QualifiedOwnerResolution::Unresolved
10163        }
10164    }
10165}
10166
10167fn qualified_callable_owner_components(
10168    node: Node<'_>,
10169    source: &str,
10170) -> Option<(Vec<String>, bool)> {
10171    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10172        || !qualified_name_has_concrete_scope_separators(node)
10173    {
10174        return None;
10175    }
10176    let owner = qualified_owner_components(node, source)?;
10177    Some((owner.names, owner.global))
10178}
10179
10180fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
10181    if !matches!(
10182        node.kind(),
10183        "identifier"
10184            | "type_identifier"
10185            | "namespace_identifier"
10186            | "qualified_identifier"
10187            | "scoped_identifier"
10188            | "scoped_type_identifier"
10189            | "template_type"
10190            | "template_function"
10191    ) {
10192        return None;
10193    }
10194    let components = cpp_type_name_components(node, source)?;
10195    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
10196}
10197
10198/// The block-scope answer for a type node, when a local class declaration
10199/// decides it: `Some(None)` when a block declares the name but it denotes no
10200/// minted class, so callers must not fall back to an outer type.
10201fn function_local_type_for_node(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<Option<CodeUnit>> {
10202    let (components, global) = type_reference_components(node, ctx.source)?;
10203    if global {
10204        return None;
10205    }
10206    match function_local_type_resolution(&ctx.analyzer, ctx.file, node, &components, ctx.source)? {
10207        LexicalTypeResolution::Resolved { unit, .. } => Some(Some(unit)),
10208        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => Some(None),
10209    }
10210}
10211
10212pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
10213    let mut namespaces = Vec::new();
10214    for parent in enclosing_cpp_namespace_nodes(node) {
10215        if let Some(name) = parent.child_by_field_name("name") {
10216            let mut components = Vec::new();
10217            if append_cpp_name_components(name, source, &mut components).is_some() {
10218                namespaces.push(components);
10219            }
10220        }
10221    }
10222    namespaces.into_iter().flatten().collect()
10223}
10224
10225pub fn enclosing_lexical_scope_components(
10226    node: Node<'_>,
10227    analyzer: &CppGraphSource<'_>,
10228    visibility: &VisibilityIndex<'_>,
10229    file: &ProjectFile,
10230    source: &str,
10231) -> LexicalScopeResolution {
10232    enclosing_lexical_scope_components_with_unresolved_owner(
10233        node,
10234        analyzer,
10235        visibility,
10236        file,
10237        source,
10238        false,
10239        false,
10240        &orphaned_namespace_scopes(visibility, file),
10241    )
10242}
10243
10244#[allow(clippy::too_many_arguments)]
10245fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
10246    node: Node<'_>,
10247    analyzer: &CppGraphSource<'_>,
10248    visibility: &VisibilityIndex<'_>,
10249    file: &ProjectFile,
10250    source: &str,
10251    allow_structured_unresolved_owner: bool,
10252    ignore_function_owner: bool,
10253    cache: Option<&LexicalScopeCache>,
10254) -> LexicalScopeResolution {
10255    let Some(cache) = cache else {
10256        return enclosing_lexical_scope_components_with_unresolved_owner(
10257            node,
10258            analyzer,
10259            visibility,
10260            file,
10261            source,
10262            allow_structured_unresolved_owner,
10263            ignore_function_owner,
10264            &orphaned_namespace_scopes(visibility, file),
10265        );
10266    };
10267    let orphaned = &cache.orphaned;
10268    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
10269    // A namespace body or translation unit anchor spans every recovered
10270    // region among its children, whose scopes differ; the region bounds keep
10271    // the key exact there and leave an anchor inside one region unchanged.
10272    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
10273        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
10274        None => (anchor_start, anchor_end),
10275    };
10276    let key = (
10277        anchor_start,
10278        anchor_end,
10279        allow_structured_unresolved_owner,
10280        ignore_function_owner,
10281    );
10282    if let Some(cached) = cache
10283        .resolutions
10284        .read(&visibility.reads.memo_attribution(), |cells| {
10285            cells.borrow().get(&key).cloned()
10286        })
10287    {
10288        return cached;
10289    }
10290    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
10291        node,
10292        analyzer,
10293        visibility,
10294        file,
10295        source,
10296        allow_structured_unresolved_owner,
10297        ignore_function_owner,
10298        orphaned,
10299    );
10300    cache
10301        .resolutions
10302        .read(&visibility.reads.memo_attribution(), |cells| {
10303            cells.borrow_mut().insert(key, resolved.clone())
10304        });
10305    resolved
10306}
10307
10308fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
10309    let mut current = node;
10310    loop {
10311        if matches!(
10312            current.kind(),
10313            "function_definition"
10314                | "class_specifier"
10315                | "struct_specifier"
10316                | "union_specifier"
10317                | "namespace_definition"
10318                | "translation_unit"
10319        ) {
10320            return (current.start_byte(), current.end_byte());
10321        }
10322        let Some(parent) = current.parent() else {
10323            return (current.start_byte(), current.end_byte());
10324        };
10325        current = parent;
10326    }
10327}
10328
10329#[allow(clippy::too_many_arguments)]
10330fn enclosing_lexical_scope_components_with_unresolved_owner(
10331    node: Node<'_>,
10332    analyzer: &CppGraphSource<'_>,
10333    visibility: &VisibilityIndex<'_>,
10334    file: &ProjectFile,
10335    source: &str,
10336    allow_structured_unresolved_owner: bool,
10337    ignore_function_owner: bool,
10338    orphaned: &OrphanedNamespaceScopeIndex,
10339) -> LexicalScopeResolution {
10340    #[cfg(any(test, feature = "test-support"))]
10341    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
10342    // One ancestor climb collects the namespace chain, the class chain, the
10343    // nearest function definition and both displaced-class-shape facts.
10344    // `Node::parent` re-descends from the root on every call (tree-sitter
10345    // 0.24+), so the four separate climbs this replaces each cost another
10346    // near-full-AST scan per reconstruction on a large flat file (#1927).
10347    let mut namespaces = Vec::new();
10348    let mut classes = Vec::new();
10349    let mut function_definition = None;
10350    let mut displaced_class_scope = false;
10351    let mut current = node.parent();
10352    while let Some(parent) = current {
10353        match parent.kind() {
10354            "namespace_definition" => {
10355                if let Some(name) = parent.child_by_field_name("name") {
10356                    let mut components = Vec::new();
10357                    if append_cpp_name_components(name, source, &mut components).is_some() {
10358                        namespaces.push((
10359                            parent.start_byte(),
10360                            components,
10361                            namespace_definition_has_real_braces(parent),
10362                        ));
10363                    }
10364                }
10365            }
10366            "class_specifier" | "struct_specifier" | "union_specifier" => {
10367                if let Some(name) = parent.child_by_field_name("name") {
10368                    let mut components = Vec::new();
10369                    if append_cpp_name_components(name, source, &mut components).is_some() {
10370                        classes.push(components);
10371                    }
10372                }
10373            }
10374            "function_definition" => {
10375                if function_definition.is_none() {
10376                    function_definition = Some(parent);
10377                }
10378                displaced_class_scope = displaced_class_scope
10379                    || parent.child_by_field_name("type").is_some_and(|type_node| {
10380                        matches!(
10381                            type_node.kind(),
10382                            "class_specifier" | "struct_specifier" | "union_specifier"
10383                        )
10384                    })
10385                    || is_malformed_wrapper_function_definition(parent);
10386            }
10387            _ => {}
10388        }
10389        current = parent.parent();
10390    }
10391    namespaces.reverse();
10392    // The parsed chain can stop short of the namespaces tree-sitter's error
10393    // recovery closed early; the orphaned index restores them (#1537).
10394    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
10395    let mut scope = namespace.clone();
10396    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
10397    // qualified function declarator. It is recovery scaffolding, not a real
10398    // callable owner, and must not overwrite the indexed class scope retained
10399    // by the wrapper body (#2249).
10400    let has_qualified_function_owner = function_definition
10401        .filter(|function| !is_malformed_wrapper_function_definition(*function))
10402        .and_then(function_definition_owner_lookup_node)
10403        .is_some_and(|owner| {
10404            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10405        });
10406    let indexed_scope = displaced_class_scope
10407        .then(|| {
10408            indexed_structural_class_scope(visibility, file, node, source)
10409                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10410        })
10411        .flatten()
10412        .or_else(|| {
10413            // A qualified out-of-line definition can lose its class owner from
10414            // the parser tree when a namespace sentinel or export macro wraps
10415            // the declaration.  Recover the indexed owner scope up front so
10416            // all unqualified type references in the body see the same class
10417            // boundary as C++ lookup, including aliases in parameters and
10418            // local declarations (not only template-argument leaves).
10419            (has_qualified_function_owner && function_definition.is_some())
10420                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10421                .flatten()
10422                .filter(|indexed| {
10423                    qualified_owner_scope_is_recoverable(
10424                        indexed,
10425                        &namespace,
10426                        &classes,
10427                        function_definition
10428                            .and_then(function_definition_owner_lookup_node)
10429                            .and_then(|owner| qualified_callable_owner_components(owner, source))
10430                            .map(|(components, _)| components),
10431                    )
10432                })
10433        })
10434        .or_else(|| {
10435            // A nested class declaration can likewise lose one of its outer
10436            // class ancestors from the CST.  Prefer the exact indexed
10437            // structural class scope. Its same-file declaration range and
10438            // exact template-id match already prove the owner, including a
10439            // specialization whose parser component retains only the primary
10440            // name. Keep the suffix guard for graph-only recovery, which lacks
10441            // that direct syntax-range proof.
10442            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10443                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10444                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10445                })
10446            })
10447        })
10448        .or_else(|| {
10449            // Retain the existing indexed lexical-scope recovery for
10450            // unqualified function bodies.  It is intentionally last so a
10451            // canonical class owner wins whenever one is available.
10452            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10453                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10454                .flatten()
10455                .filter(|indexed| indexed.len() > namespace.len())
10456        });
10457    if let Some(indexed_scope) = indexed_scope.as_ref() {
10458        // A macro-displaced namespace can leave the parser with the real class
10459        // body but no namespace ancestor. Prefer the structural class match;
10460        // partial specializations whose structured name cannot round-trip use
10461        // the exact indexed enclosing-owner chain instead.
10462        scope = indexed_scope.clone();
10463        classes.clear();
10464    }
10465
10466    if !ignore_function_owner
10467        && has_qualified_function_owner
10468        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10469    {
10470        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10471            return LexicalScopeResolution::Missing;
10472        };
10473        // Resolve the out-of-line owner from the parser namespace before the
10474        // provisional indexed parent can influence the answer. Per-file
10475        // extraction can assign the first same-depth using namespace to a
10476        // bare owner. The structured using resolver instead selects the
10477        // namespace whose visible class has the owner name (#1838).
10478        let imports = visibility.ordinary_type_import_cell(file);
10479        let owner_resolution = resolve_type_components_lexically_at_scoped(
10480            function,
10481            &owner,
10482            global,
10483            analyzer,
10484            visibility,
10485            &imports,
10486            file,
10487            source,
10488            None,
10489            false,
10490            false,
10491            false,
10492            namespace.clone(),
10493        );
10494        match owner_resolution {
10495            LexicalTypeResolution::Resolved {
10496                unit, components, ..
10497            } if is_indexed_class_owner(analyzer, &unit) => {
10498                scope = components;
10499                classes.clear();
10500            }
10501            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10502            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10503                match visibility
10504                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10505                {
10506                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10507                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10508                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10509                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10510                            qualified_owner_scope_is_recoverable(
10511                                indexed,
10512                                &namespace,
10513                                &classes,
10514                                Some(owner.clone()),
10515                            )
10516                        }) {
10517                            scope = indexed.clone();
10518                        } else {
10519                            scope = if global || owner.starts_with(&namespace) {
10520                                owner
10521                            } else {
10522                                let mut relative = namespace;
10523                                relative.extend(owner);
10524                                relative
10525                            };
10526                        }
10527                    }
10528                    LexicalTypeResolution::Missing => {
10529                        // Structural lexical resolution cannot see an owner class that
10530                        // is reachable only through an in-scope `using namespace`
10531                        // directive, so it would otherwise hard-fail here. The indexed
10532                        // definition already carries the true fully-qualified owner
10533                        // (its package reflects the directive), so recover the real
10534                        // enclosing scope from the analyzer graph -- exactly the scope
10535                        // chain real C++ unqualified lookup traverses. Only the strict
10536                        // callers reach this arm; the best-effort callers above keep
10537                        // their existing structural guess (and its query profile).
10538                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10539                            .or_else(|| {
10540                                indexed_namespace_qualified_scope(
10541                                    analyzer, visibility, file, node, &owner,
10542                                )
10543                            }) {
10544                            Some(indexed) => scope = indexed,
10545                            None => return LexicalScopeResolution::Missing,
10546                        }
10547                    }
10548                }
10549            }
10550        }
10551    }
10552
10553    classes.reverse();
10554    scope.extend(classes.into_iter().flatten());
10555    LexicalScopeResolution::Resolved(scope)
10556}
10557
10558fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10559    let mut current = node.parent();
10560    while let Some(parent) = current {
10561        if parent.kind() == "function_definition"
10562            && parent.child_by_field_name("type").is_some_and(|type_node| {
10563                matches!(
10564                    type_node.kind(),
10565                    "class_specifier" | "struct_specifier" | "union_specifier"
10566                )
10567            })
10568        {
10569            return true;
10570        }
10571        current = parent.parent();
10572    }
10573    false
10574}
10575
10576fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10577    let mut current = node.parent();
10578    while let Some(parent) = current {
10579        if parent.kind() == "function_definition"
10580            && is_malformed_wrapper_function_definition(parent)
10581        {
10582            return true;
10583        }
10584        current = parent.parent();
10585    }
10586    false
10587}
10588
10589/// Whether a function definition is a recovery wrapper with a non-function
10590/// declarator. The older grammar's evidence is parser damage; the
10591/// macro-declaration grammar keeps the same envelope clean, where a
10592/// `macro_modifier`/`macro_modifier_declarator`/`macro_type_specifier` head is
10593/// admitted only where the parse would otherwise contain an error.
10594pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10595    let mut cursor = node.walk();
10596    let recovery_evidence = node.has_error()
10597        || node.named_children(&mut cursor).any(|child| {
10598            matches!(
10599                child.kind(),
10600                "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10601            )
10602        });
10603    recovery_evidence
10604        && node
10605            .child_by_field_name("declarator")
10606            .is_some_and(|declarator| {
10607                declarator.kind() != "function_declarator"
10608                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10609            })
10610}
10611
10612/// Tree-sitter can make an attribute/nullability macro look like the namespace
10613/// component of a qualified function owner when it appears between the return
10614/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10615/// The recovered owner is not a C++ lexical owner, so callers resolving the
10616/// ordinary return/parameter type must retain the surrounding namespace scope.
10617fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10618    node.child_by_field_name("scope")
10619        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10620        .is_some()
10621}
10622
10623fn indexed_structural_class_scope(
10624    visibility: &VisibilityIndex<'_>,
10625    file: &ProjectFile,
10626    node: Node<'_>,
10627    source: &str,
10628) -> Option<Vec<String>> {
10629    let mut current = node.parent();
10630    while let Some(parent) = current {
10631        if matches!(
10632            parent.kind(),
10633            "class_specifier" | "struct_specifier" | "union_specifier"
10634        ) {
10635            return visibility.indexed_structural_class_scope(file, parent, source);
10636        }
10637        current = parent.parent();
10638    }
10639    None
10640}
10641
10642/// Check that an indexed owner scope is a structured completion of the parser
10643/// scope rather than an unrelated same-spelled declaration.
10644///
10645/// Error recovery around C++ namespace sentinels can preserve only a subset of
10646/// the namespace/class chain.  The indexed definition still carries the full
10647/// owner path, so require every surviving parser component to occur in order
10648/// and require any explicit qualified function owner to be the terminal
10649/// suffix.  An empty parser scope is accepted only with that qualified-owner
10650/// suffix evidence; a lone top-level short name is not evidence that a
10651/// namespace was lost.
10652fn qualified_owner_scope_is_recoverable(
10653    indexed: &[String],
10654    namespace: &[String],
10655    classes: &[Vec<String>],
10656    qualified_owner: Option<Vec<String>>,
10657) -> bool {
10658    if let Some(owner) = qualified_owner {
10659        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10660            return false;
10661        }
10662        // A malformed namespace sentinel can erase every parser namespace
10663        // ancestor.  The indexed enclosing callable still provides an
10664        // authoritative class owner, so the qualified owner suffix itself is
10665        // enough evidence in that case.  When namespace components survived,
10666        // retain the stricter subsequence check below.
10667        if namespace.is_empty() {
10668            return true;
10669        }
10670        if indexed.len() <= namespace.len() {
10671            return false;
10672        }
10673        return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10674    }
10675    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10676    if !class_components.is_empty() {
10677        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10678    }
10679    if namespace.is_empty() || indexed.len() <= namespace.len() {
10680        return false;
10681    }
10682    super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10683}
10684
10685/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10686/// counts as the true lexical owner recovered from the indexed graph.
10687fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10688    unit.is_class()
10689        && !analyzer
10690            .type_alias_provider()
10691            .is_some_and(|provider| provider.is_type_alias(unit))
10692}
10693
10694/// Recover the enclosing member's true lexical scope from the *indexed*
10695/// definition when structural resolution cannot see the owner class.
10696///
10697/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10698/// {...}`) whose owner class is reachable only through an in-scope `using
10699/// namespace X;` directive cannot be resolved by `resolve_type_components_
10700/// lexically`, which walks structural lexical tiers and never consults
10701/// using-directives. The definition itself, however, is indexed with its true
10702/// fully-qualified identity (its package already reflects the directive), so
10703/// the analyzer graph knows the real owner. Walk from the reference's indexed
10704/// enclosing code unit up to the innermost enclosing class and return that
10705/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10706/// -- exactly the scope chain C++ unqualified lookup traverses.
10707fn indexed_enclosing_owner_scope(
10708    analyzer: &CppGraphSource<'_>,
10709    visibility: &VisibilityIndex<'_>,
10710    file: &ProjectFile,
10711    node: Node<'_>,
10712) -> Option<Vec<String>> {
10713    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10714}
10715
10716/// Recover the lexical scope of an out-of-line definition whose syntactic
10717/// qualifier names a *namespace* rather than a class (`void out::target(int)
10718/// {...}` written at file scope, issue #3096).
10719///
10720/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10721/// nothing here and the strict caller would fail closed for every unqualified
10722/// reference in the body. C++ unqualified lookup inside such a definition
10723/// proceeds from the named namespace outward, exactly as inside a `namespace
10724/// out { ... }` block, and the indexed definition already carries that scope
10725/// (`["out"]` for `out::target`).
10726///
10727/// Take it only when a namespace of that name is visible from the file and the
10728/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10729/// the graph does not hold (`void MissingContainer::call()`) has no such
10730/// namespace, so it still fails closed -- and it is rejected by the in-memory
10731/// visibility index, before any indexed enclosing-unit query.
10732fn indexed_namespace_qualified_scope(
10733    analyzer: &CppGraphSource<'_>,
10734    visibility: &VisibilityIndex<'_>,
10735    file: &ProjectFile,
10736    node: Node<'_>,
10737    owner: &[String],
10738) -> Option<Vec<String>> {
10739    let name = owner.last().expect("a qualified owner has one component");
10740    if !visibility
10741        .visible_identifier_candidates(file, name)
10742        .any(|unit| unit.is_module())
10743    {
10744        return None;
10745    }
10746    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10747    indexed.ends_with(owner).then_some(indexed)
10748}
10749
10750fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10751    let start = enclosing_context(node, ctx).enclosing?;
10752    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10753        ctx.analyzer.parent_of(unit)
10754    })
10755    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10756}
10757
10758/// How a qualified type's qualifier selects the owner of the member it names.
10759pub enum QualifierOwnerSelection {
10760    /// No qualifier applies template arguments or names an alias, or the
10761    /// arguments select the primary template, which the ordinary component
10762    /// lookup already names.
10763    Unchanged,
10764    /// The qualifier selects an owner the plain spelling does not reach: a
10765    /// specialization its arguments name, or the class an alias qualifier
10766    /// denotes. Look the rest of the path up through the owner's own scope
10767    /// components.
10768    Selected(Vec<String>),
10769    /// The arguments cannot be bound, more than one specialization applies,
10770    /// or the alias arms the reference admits disagree. The reference stays
10771    /// unproven rather than falling back to the primary template's member.
10772    Ambiguous,
10773}
10774
10775/// Select the owner a qualifier such as `Owner<int>` in `Owner<int>::Alias`
10776/// names before its member is looked up (#6902 item 9).
10777///
10778/// Component lookup reads a qualified name as plain spellings, so it drops
10779/// the qualifier's template arguments and always reaches the primary
10780/// template's member. The qualifier prefix is resolved lexically to its
10781/// primary template, and the shared argument binding and specialization
10782/// selection ([`VisibilityIndex::resolve_template_arguments`]) pick the class
10783/// those arguments name, the same choice an unqualified `Owner<int>` makes.
10784/// Only the last qualifier that applies arguments is selected; a deeper one,
10785/// as in `A<int>::B<char>::C`, keeps the ordinary lookup. A qualifier without
10786/// arguments may still be an alias of a specialization
10787/// ([`select_alias_qualifier_owner`]).
10788#[allow(clippy::too_many_arguments)]
10789pub fn select_qualifier_owner(
10790    node: Node<'_>,
10791    global: bool,
10792    analyzer: &CppGraphSource<'_>,
10793    visibility: &VisibilityIndex<'_>,
10794    ordinary_type_imports: &OrdinaryTypeImportCell,
10795    file: &ProjectFile,
10796    source: &str,
10797) -> QualifierOwnerSelection {
10798    if !matches!(
10799        node.kind(),
10800        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10801    ) {
10802        return QualifierOwnerSelection::Unchanged;
10803    }
10804    let Some(components) = cpp_name_component_nodes(node) else {
10805        return QualifierOwnerSelection::Unchanged;
10806    };
10807    fn templated(component: Node<'_>) -> Option<Node<'_>> {
10808        component.parent().filter(|parent| {
10809            parent.kind() == "template_type"
10810                && parent.child_by_field_name("name") == Some(component)
10811        })
10812    }
10813    let qualifiers = &components[..components.len().saturating_sub(1)];
10814    let Some((index, template)) = qualifiers
10815        .iter()
10816        .enumerate()
10817        .rev()
10818        .find_map(|(index, component)| templated(*component).map(|template| (index, template)))
10819    else {
10820        return select_alias_qualifier_owner(
10821            node,
10822            &components,
10823            global,
10824            analyzer,
10825            visibility,
10826            file,
10827            source,
10828        );
10829    };
10830    if qualifiers[..index]
10831        .iter()
10832        .any(|component| templated(*component).is_some())
10833    {
10834        return QualifierOwnerSelection::Unchanged;
10835    }
10836    let Some(arguments) = cpp_template_reference_arguments(template, source) else {
10837        return QualifierOwnerSelection::Unchanged;
10838    };
10839    let names = components
10840        .iter()
10841        .map(|component| node_text(*component, source).to_owned())
10842        .collect::<Vec<_>>();
10843    let primary = match resolve_type_components_lexically_at(
10844        node,
10845        &names[..=index],
10846        global,
10847        analyzer,
10848        visibility,
10849        ordinary_type_imports,
10850        file,
10851        source,
10852    ) {
10853        LexicalTypeResolution::Resolved { unit, .. } => unit,
10854        LexicalTypeResolution::Ambiguous => return QualifierOwnerSelection::Ambiguous,
10855        LexicalTypeResolution::Missing => return QualifierOwnerSelection::Unchanged,
10856    };
10857    match visibility.resolve_template_arguments(file, primary.clone(), &arguments) {
10858        Ok(owner) if same_visible_symbol(&owner, &primary) => QualifierOwnerSelection::Unchanged,
10859        Ok(owner) => {
10860            let mut redirected = canonical_cpp_scope_components(&owner);
10861            redirected.extend_from_slice(&names[index + 1..]);
10862            QualifierOwnerSelection::Selected(redirected)
10863        }
10864        Err(_) => QualifierOwnerSelection::Ambiguous,
10865    }
10866}
10867
10868/// The owner an alias qualifier such as `Selected` in `Selected::Alias`
10869/// denotes.
10870///
10871/// `using Selected = Owner<int>;` keeps the arguments in the alias's own
10872/// declaration, so the qualifier resolves through the alias to the selected
10873/// specialization, never to the primary template that the spelling `Owner`
10874/// would reach. The alias declarations the reference's own guards admit are
10875/// read directly: conditional arms that select different owners leave the
10876/// member undecided, and only arms that all agree decide it.
10877fn select_alias_qualifier_owner(
10878    node: Node<'_>,
10879    components: &[Node<'_>],
10880    global: bool,
10881    analyzer: &CppGraphSource<'_>,
10882    visibility: &VisibilityIndex<'_>,
10883    file: &ProjectFile,
10884    source: &str,
10885) -> QualifierOwnerSelection {
10886    let Some((terminal, qualifiers)) = components.split_last() else {
10887        return QualifierOwnerSelection::Unchanged;
10888    };
10889    let Some(last_qualifier) = qualifiers.last() else {
10890        return QualifierOwnerSelection::Unchanged;
10891    };
10892    let names = qualifiers
10893        .iter()
10894        .map(|component| node_text(*component, source).to_owned())
10895        .collect::<Vec<_>>();
10896    let declared = visibility
10897        .visible_identifier_candidates(file, node_text(*last_qualifier, source))
10898        .filter(|candidate| declared_type_alias(analyzer, candidate))
10899        .filter(|candidate| {
10900            let scope = canonical_cpp_scope_components(candidate);
10901            if global {
10902                scope == names
10903            } else {
10904                scope.ends_with(&names)
10905            }
10906        })
10907        .collect::<Vec<_>>();
10908    let aliases = declared
10909        .iter()
10910        .filter(|candidate| {
10911            visibility.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10912        })
10913        .map(|alias| visibility.canonical_type_unit_in_context(analyzer, file, node, alias))
10914        .collect::<Vec<_>>();
10915    let Some((first, rest)) = aliases.split_first() else {
10916        return QualifierOwnerSelection::Unchanged;
10917    };
10918    let Some(owner) = first else {
10919        return QualifierOwnerSelection::Unchanged;
10920    };
10921    if !rest.iter().all(|target| {
10922        target
10923            .as_ref()
10924            .is_some_and(|target| same_visible_symbol(target, owner))
10925    }) {
10926        return QualifierOwnerSelection::Ambiguous;
10927    }
10928    // Ordinary lookup already follows an unconditional alias to a class it
10929    // can name by spelling. It cannot name a specialization, and conditional
10930    // arms leave it undecided even when the arms the reference admits agree,
10931    // so the decided owner is used in those two cases only.
10932    let conditional = aliases.len() < declared.len();
10933    if !owner.is_class() || (!conditional && !visibility.is_template_specialization(owner)) {
10934        return QualifierOwnerSelection::Unchanged;
10935    }
10936    let mut redirected = canonical_cpp_scope_components(owner);
10937    redirected.push(node_text(*terminal, source).to_owned());
10938    QualifierOwnerSelection::Selected(redirected)
10939}
10940
10941pub fn resolve_type_node_lexically(
10942    node: Node<'_>,
10943    analyzer: &CppGraphSource<'_>,
10944    visibility: &VisibilityIndex<'_>,
10945    ordinary_type_imports: &OrdinaryTypeImportCell,
10946    file: &ProjectFile,
10947    source: &str,
10948) -> LexicalTypeResolution {
10949    let Some((components, global)) = type_reference_components(node, source) else {
10950        return LexicalTypeResolution::Missing;
10951    };
10952    match select_qualifier_owner(
10953        node,
10954        global,
10955        analyzer,
10956        visibility,
10957        ordinary_type_imports,
10958        file,
10959        source,
10960    ) {
10961        QualifierOwnerSelection::Unchanged => {}
10962        QualifierOwnerSelection::Selected(components) => {
10963            return resolve_type_components_lexically_at(
10964                node,
10965                &components,
10966                true,
10967                analyzer,
10968                visibility,
10969                ordinary_type_imports,
10970                file,
10971                source,
10972            );
10973        }
10974        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
10975    }
10976    let resolution = resolve_type_components_lexically_at(
10977        node,
10978        &components,
10979        global,
10980        analyzer,
10981        visibility,
10982        ordinary_type_imports,
10983        file,
10984        source,
10985    );
10986    if !is_cpp_template_argument_type_leaf(node) {
10987        return resolution;
10988    }
10989
10990    // Error recovery can detach a member function from its class while
10991    // leaving an unqualified type argument (for example `error_type` in
10992    // `expected<..., error_type>`). The normal structural scope then lacks
10993    // the class owner and resolves the wrong same-spelled alias, or fails
10994    // closed. The indexed enclosing unit still carries the authoritative
10995    // class scope; retry only this narrowly-shaped leaf with that scope.
10996    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10997        return resolution;
10998    };
10999    let namespace_scope = enclosing_namespace_components(node, source);
11000    if indexed_scope.len() <= namespace_scope.len() {
11001        return resolution;
11002    }
11003    let indexed = resolve_type_components_in_authoritative_scope(
11004        node,
11005        &components,
11006        global,
11007        analyzer,
11008        visibility,
11009        ordinary_type_imports,
11010        file,
11011        source,
11012        None,
11013        false,
11014        false,
11015        false,
11016        indexed_scope,
11017    );
11018    match indexed {
11019        LexicalTypeResolution::Resolved { ref unit, .. }
11020            if !visibility
11021                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11022        {
11023            resolution
11024        }
11025        LexicalTypeResolution::Resolved { .. } => indexed,
11026        _ => resolution,
11027    }
11028}
11029
11030/// Resolve a type lexically, using the target to guide incomplete evidence.
11031/// A lexical resolution visible in context wins over target guidance even when
11032/// its unit and candidates do not include the target.
11033#[allow(clippy::too_many_arguments)]
11034pub fn resolve_type_node_lexically_for_target(
11035    node: Node<'_>,
11036    analyzer: &CppGraphSource<'_>,
11037    visibility: &VisibilityIndex<'_>,
11038    ordinary_type_imports: &OrdinaryTypeImportCell,
11039    file: &ProjectFile,
11040    source: &str,
11041    target: &CodeUnit,
11042    scope_cache: Option<&LexicalScopeCache>,
11043    recovered_scope: Option<&[String]>,
11044) -> LexicalTypeResolution {
11045    let Some((reference_components, global)) = type_reference_components(node, source) else {
11046        return LexicalTypeResolution::Missing;
11047    };
11048    let terminal = reference_components
11049        .last()
11050        .expect("type reference components are non-empty");
11051    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
11052        return LexicalTypeResolution::Missing;
11053    }
11054    let template_arguments = cpp_template_reference_arguments(node, source);
11055    let selects_concrete_specialization =
11056        template_arguments.is_some() && visibility.is_template_specialization(target);
11057    if !selects_concrete_specialization
11058        && !visibility.structured_type_reference_may_resolve_to_target(
11059            analyzer,
11060            file,
11061            std::slice::from_ref(terminal),
11062            false,
11063            &[],
11064            target,
11065        )
11066    {
11067        return LexicalTypeResolution::Missing;
11068    }
11069    if let Some(arguments) = template_arguments.as_ref() {
11070        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
11071            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
11072                node,
11073                &reference_components,
11074                global,
11075                analyzer,
11076                visibility,
11077                ordinary_type_imports,
11078                file,
11079                source,
11080                recovered_scope,
11081            )
11082        } else {
11083            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
11084                node,
11085                &reference_components,
11086                global,
11087                analyzer,
11088                visibility,
11089                ordinary_type_imports,
11090                file,
11091                source,
11092                scope_cache,
11093            )
11094        };
11095        return match alias_resolution {
11096            LexicalTypeResolution::Resolved {
11097                unit,
11098                components,
11099                candidates,
11100            } if visibility.template_alias_arguments_preserve_target(
11101                analyzer, file, &unit, arguments, target,
11102            ) =>
11103            {
11104                LexicalTypeResolution::Resolved {
11105                    unit: target.clone(),
11106                    components,
11107                    candidates,
11108                }
11109            }
11110            LexicalTypeResolution::Resolved {
11111                unit,
11112                components,
11113                candidates,
11114            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
11115                Ok(resolved_unit) => {
11116                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
11117                        .then(|| {
11118                            target_guided_malformed_template_alias_resolution(
11119                                node,
11120                                analyzer,
11121                                visibility,
11122                                file,
11123                                arguments,
11124                                &reference_components,
11125                                target,
11126                            )
11127                        })
11128                        .flatten();
11129                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
11130                        unit: resolved_unit,
11131                        components,
11132                        candidates,
11133                    })
11134                }
11135                Err(_) => LexicalTypeResolution::Ambiguous,
11136            },
11137            LexicalTypeResolution::Missing => {
11138                let target_preserving = if let Some(recovered_scope) = recovered_scope {
11139                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
11140                        node,
11141                        &reference_components,
11142                        global,
11143                        analyzer,
11144                        visibility,
11145                        ordinary_type_imports,
11146                        file,
11147                        source,
11148                        target,
11149                        true,
11150                        recovered_scope,
11151                    )
11152                } else {
11153                    resolve_type_components_lexically_at_for_target_with_scope_cache(
11154                        node,
11155                        &reference_components,
11156                        global,
11157                        analyzer,
11158                        visibility,
11159                        ordinary_type_imports,
11160                        file,
11161                        source,
11162                        target,
11163                        true,
11164                        scope_cache,
11165                    )
11166                };
11167                match target_preserving {
11168                    LexicalTypeResolution::Resolved {
11169                        unit: _,
11170                        components,
11171                        candidates,
11172                    } if template_reference_candidates_select_target(
11173                        node,
11174                        &candidates,
11175                        analyzer,
11176                        visibility,
11177                        file,
11178                        source,
11179                        target,
11180                    ) =>
11181                    {
11182                        LexicalTypeResolution::Resolved {
11183                            unit: target.clone(),
11184                            components,
11185                            candidates,
11186                        }
11187                    }
11188                    _ => target_guided_malformed_template_alias_resolution(
11189                        node,
11190                        analyzer,
11191                        visibility,
11192                        file,
11193                        arguments,
11194                        &reference_components,
11195                        target,
11196                    )
11197                    .unwrap_or(LexicalTypeResolution::Missing),
11198                }
11199            }
11200            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
11201        };
11202    }
11203    match select_qualifier_owner(
11204        node,
11205        global,
11206        analyzer,
11207        visibility,
11208        ordinary_type_imports,
11209        file,
11210        source,
11211    ) {
11212        QualifierOwnerSelection::Unchanged => {}
11213        QualifierOwnerSelection::Selected(components) => {
11214            return resolve_type_components_lexically_at_for_target_with_scope_cache(
11215                node,
11216                &components,
11217                true,
11218                analyzer,
11219                visibility,
11220                ordinary_type_imports,
11221                file,
11222                source,
11223                target,
11224                true,
11225                scope_cache,
11226            );
11227        }
11228        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
11229    }
11230    let resolution = if let Some(recovered_scope) = recovered_scope {
11231        resolve_type_components_lexically_at_for_target_with_recovered_scope(
11232            node,
11233            &reference_components,
11234            global,
11235            analyzer,
11236            visibility,
11237            ordinary_type_imports,
11238            file,
11239            source,
11240            target,
11241            true,
11242            recovered_scope,
11243        )
11244    } else {
11245        resolve_type_components_lexically_at_for_target_with_scope_cache(
11246            node,
11247            &reference_components,
11248            global,
11249            analyzer,
11250            visibility,
11251            ordinary_type_imports,
11252            file,
11253            source,
11254            target,
11255            true,
11256            scope_cache,
11257        )
11258    };
11259    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
11260        target_guided_qualified_namespace_function_type_resolution(
11261            node,
11262            &reference_components,
11263            global,
11264            analyzer,
11265            visibility,
11266            ordinary_type_imports,
11267            file,
11268            source,
11269            target,
11270        )
11271        .unwrap_or(resolution)
11272    } else {
11273        resolution
11274    };
11275    if !is_cpp_template_argument_type_leaf(node) {
11276        return resolution;
11277    }
11278
11279    // Preprocessor recovery can lift a member declaration out of its class
11280    // field list.  The unqualified template argument is then resolved from
11281    // the namespace only, even though the indexed enclosing callable still
11282    // identifies the class owner.  Retry this exact leaf against that
11283    // structured owner scope; ordinary type nodes must continue to use the
11284    // parser-derived lexical scope so unrelated same-spelled aliases remain
11285    // excluded.
11286    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
11287        return resolution;
11288    };
11289    let namespace_scope = enclosing_namespace_components(node, source);
11290    if indexed_scope.len() <= namespace_scope.len() {
11291        return resolution;
11292    }
11293    let indexed = visibility.resolve_type_components_lexically_for_target(
11294        analyzer,
11295        file,
11296        &reference_components,
11297        global,
11298        &indexed_scope,
11299        target,
11300    );
11301    match indexed {
11302        LexicalTypeResolution::Resolved { ref unit, .. }
11303            if visibility
11304                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11305        {
11306            indexed
11307        }
11308        _ => resolution,
11309    }
11310}
11311
11312/// Recover the lexical namespace of an out-of-line namespace function whose
11313/// relative qualifier is reachable through a `using namespace` directive.
11314///
11315/// Per-file declaration extraction can attach `void schema::consume(...)` to
11316/// the first visible namespace prefix when more than one using-directive is
11317/// active. A matching visible free-function declaration still proves the
11318/// complete namespace. Target-guided inverse lookup may use that namespace to
11319/// retry a parameter or body type, but only when the target lives in the same
11320/// namespace and the declaration's callable arity matches the definition.
11321#[allow(clippy::too_many_arguments)]
11322fn target_guided_qualified_namespace_function_type_resolution(
11323    node: Node<'_>,
11324    components: &[String],
11325    global: bool,
11326    analyzer: &CppGraphSource<'_>,
11327    visibility: &VisibilityIndex<'_>,
11328    ordinary_type_imports: &OrdinaryTypeImportCell,
11329    file: &ProjectFile,
11330    source: &str,
11331    target: &CodeUnit,
11332) -> Option<LexicalTypeResolution> {
11333    let function_definition = std::iter::successors(Some(node), |current| current.parent())
11334        .find(|current| current.kind() == "function_definition")?;
11335    let function = function_definition_name_node(function_definition)?;
11336    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
11337    let target_namespace = target.package_name();
11338    if target_namespace.is_empty() {
11339        return None;
11340    }
11341    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11342        brokk_bifrost_core::analyzer::Language::Cpp,
11343        target_namespace,
11344    );
11345    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
11346    {
11347        return None;
11348    }
11349
11350    let function_name = node_text(function_terminal_node(function), source);
11351    let definition_unit = canonical_callable_at_node(
11352        function_definition,
11353        analyzer,
11354        visibility,
11355        file,
11356        source,
11357        TargetKind::FreeFunction,
11358    )?;
11359    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
11360    let declaration_proves_namespace = visibility
11361        .visible_identifier_candidates(file, function_name)
11362        .filter(|candidate| {
11363            candidate.is_function()
11364                && type_owner_of(analyzer, candidate).is_none()
11365                && candidate.package_name() == target_namespace
11366        })
11367        .any(|candidate| {
11368            cpp_callable_arity(analyzer, candidate)
11369                .is_none_or(|known| known.accepts(definition_arity))
11370        });
11371    if !declaration_proves_namespace {
11372        return None;
11373    }
11374
11375    let resolution = resolve_type_components_lexically_at_scoped(
11376        node,
11377        components,
11378        global,
11379        analyzer,
11380        visibility,
11381        ordinary_type_imports,
11382        file,
11383        source,
11384        Some(target),
11385        true,
11386        false,
11387        false,
11388        target_scope,
11389    );
11390    match &resolution {
11391        LexicalTypeResolution::Resolved {
11392            unit, candidates, ..
11393        } if (same_visible_symbol(unit, target)
11394            || candidates
11395                .iter()
11396                .any(|candidate| same_visible_symbol(candidate, target)))
11397            && visibility
11398                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11399        {
11400            Some(resolution)
11401        }
11402        LexicalTypeResolution::Resolved { .. }
11403        | LexicalTypeResolution::Ambiguous
11404        | LexicalTypeResolution::Missing => None,
11405    }
11406}
11407
11408#[allow(clippy::too_many_arguments)]
11409fn target_guided_malformed_template_alias_resolution(
11410    node: Node<'_>,
11411    analyzer: &CppGraphSource<'_>,
11412    visibility: &VisibilityIndex<'_>,
11413    file: &ProjectFile,
11414    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
11415    components: &[String],
11416    target: &CodeUnit,
11417) -> Option<LexicalTypeResolution> {
11418    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
11419        return None;
11420    }
11421
11422    let identifier = &components[0];
11423    let namespace =
11424        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
11425    let namespace_name = namespace.join("::");
11426    let candidates = visibility
11427        .visible_identifier_candidates(file, identifier)
11428        .filter(|candidate| {
11429            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
11430                && visibility.type_candidate_may_be_visible_before_reference(
11431                    analyzer,
11432                    file,
11433                    candidate,
11434                    node.start_byte(),
11435                )
11436        })
11437        .cloned()
11438        .collect::<Vec<_>>();
11439    let first = candidates.first()?;
11440    if !candidates
11441        .iter()
11442        .all(|candidate| same_logical_symbol(first, candidate))
11443        || !candidates.iter().all(|candidate| {
11444            visibility.template_alias_arguments_preserve_target(
11445                analyzer, file, candidate, arguments, target,
11446            )
11447        })
11448    {
11449        return None;
11450    }
11451
11452    let mut resolved_components = namespace;
11453    resolved_components.push(identifier.clone());
11454    Some(LexicalTypeResolution::Resolved {
11455        unit: target.clone(),
11456        components: resolved_components,
11457        candidates,
11458    })
11459}
11460
11461fn resolve_type_node_lexically_for_target_without_visibility(
11462    node: Node<'_>,
11463    analyzer: &CppGraphSource<'_>,
11464    visibility: &VisibilityIndex<'_>,
11465    file: &ProjectFile,
11466    source: &str,
11467    target: &CodeUnit,
11468) -> LexicalTypeResolution {
11469    let Some((components, global)) = type_reference_components(node, source) else {
11470        return LexicalTypeResolution::Missing;
11471    };
11472    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
11473        node,
11474        analyzer,
11475        visibility,
11476        file,
11477        source,
11478        true,
11479        recovered_macro_decorated_declarator_type(node)
11480            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11481        &orphaned_namespace_scopes(visibility, file),
11482    ) {
11483        LexicalScopeResolution::Resolved(scope) => scope,
11484        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11485        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11486    };
11487    visibility.resolve_type_components_lexically_for_target(
11488        analyzer,
11489        file,
11490        &components,
11491        global,
11492        &lexical_scope,
11493        target,
11494    )
11495}
11496
11497fn type_node_has_exact_target_identity_without_visibility(
11498    node: Node<'_>,
11499    analyzer: &CppGraphSource<'_>,
11500    visibility: &VisibilityIndex<'_>,
11501    file: &ProjectFile,
11502    source: &str,
11503    target: &CodeUnit,
11504) -> bool {
11505    let Some((components, global)) = type_reference_components(node, source) else {
11506        return false;
11507    };
11508    let LexicalScopeResolution::Resolved(lexical_scope) =
11509        enclosing_lexical_scope_components_with_unresolved_owner(
11510            node,
11511            analyzer,
11512            visibility,
11513            file,
11514            source,
11515            true,
11516            recovered_macro_decorated_declarator_type(node)
11517                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11518            &orphaned_namespace_scopes(visibility, file),
11519        )
11520    else {
11521        return false;
11522    };
11523    let target_name = cpp_name_for(target);
11524    lexical_component_tiers(&components, global, &lexical_scope)
11525        .any(|qualified| qualified.join("::") == target_name)
11526}
11527
11528pub fn resolve_using_enum_declaration_owner(
11529    node: Node<'_>,
11530    analyzer: &CppGraphSource<'_>,
11531    visibility: &VisibilityIndex<'_>,
11532    ordinary_type_imports: &OrdinaryTypeImportCell,
11533    file: &ProjectFile,
11534    source: &str,
11535) -> LexicalTypeResolution {
11536    let Some(type_node) = using_enum_declaration_type_node(node) else {
11537        return LexicalTypeResolution::Missing;
11538    };
11539    let mut components = Vec::new();
11540    if append_cpp_name_components(type_node, source, &mut components).is_none()
11541        || components.is_empty()
11542    {
11543        return LexicalTypeResolution::Missing;
11544    }
11545    resolve_type_components_lexically_at(
11546        type_node,
11547        &components,
11548        is_globally_qualified_cpp_name(type_node),
11549        analyzer,
11550        visibility,
11551        ordinary_type_imports,
11552        file,
11553        source,
11554    )
11555}
11556
11557pub fn resolve_ordinary_using_declaration_owner(
11558    node: Node<'_>,
11559    analyzer: &CppGraphSource<'_>,
11560    visibility: &VisibilityIndex<'_>,
11561    file: &ProjectFile,
11562    source: &str,
11563) -> LexicalTypeResolution {
11564    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
11565        return LexicalTypeResolution::Missing;
11566    };
11567    let mut components = Vec::new();
11568    if append_cpp_name_components(type_node, source, &mut components).is_none()
11569        || components.len() < 2
11570    {
11571        return LexicalTypeResolution::Missing;
11572    }
11573    let lexical_scope =
11574        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
11575            LexicalScopeResolution::Resolved(scope) => scope,
11576            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11577            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11578        };
11579    visibility.resolve_type_components_lexically(
11580        analyzer,
11581        file,
11582        &components,
11583        is_globally_qualified_cpp_name(type_node),
11584        &lexical_scope,
11585    )
11586}
11587
11588pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11589    (node.kind() == "using_declaration"
11590        && (0..node.child_count()).any(|index| {
11591            node.child(index)
11592                .is_some_and(|child| child.kind() == "enum")
11593        }))
11594    .then(|| node.named_child(0))
11595    .flatten()
11596}
11597
11598pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11599    (node.kind() == "using_declaration"
11600        && using_enum_declaration_type_node(node).is_none()
11601        && using_namespace_directive_name_node(node).is_none())
11602    .then(|| node.named_child(0))
11603    .flatten()
11604}
11605
11606/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
11607/// undefined namespace-sentinel macro as a declaration whose type is the
11608/// all-caps sentinel and whose qualified declarator starts with a pseudo
11609/// `using` scope. The real imported name remains a structured qualified
11610/// identifier under that declarator. Recover only this exact CST envelope so
11611/// ordinary macro-decorated variables are not treated as imports.
11612fn recovered_macro_using_declaration_type_node<'tree>(
11613    node: Node<'tree>,
11614    source: &str,
11615) -> Option<(Node<'tree>, bool)> {
11616    if node.kind() != "declaration" {
11617        return None;
11618    }
11619    let macro_type = node.child_by_field_name("type")?;
11620    if macro_type.kind() != "type_identifier"
11621        || !cpp_export_macro_token(node_text(macro_type, source))
11622    {
11623        return None;
11624    }
11625    let declarator = node.child_by_field_name("declarator")?;
11626    if declarator.kind() != "qualified_identifier" {
11627        return None;
11628    }
11629    let scope = declarator.child_by_field_name("scope")?;
11630    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
11631        return None;
11632    }
11633    let target = declarator.child_by_field_name("name")?;
11634    let mut components = Vec::new();
11635    append_cpp_name_components(target, source, &mut components)?;
11636    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
11637}
11638
11639fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11640    let is_directive = node.kind() == "using_directive"
11641        || (node.kind() == "using_declaration"
11642            && (0..node.child_count()).any(|index| {
11643                node.child(index)
11644                    .is_some_and(|child| child.kind() == "namespace")
11645            }));
11646    if !is_directive {
11647        return None;
11648    }
11649    node.child_by_field_name("name")
11650        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11651}
11652
11653fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11654    let mut current = node.parent();
11655    while let Some(parent) = current {
11656        if matches!(
11657            parent.kind(),
11658            "compound_statement"
11659                | "function_definition"
11660                | "lambda_expression"
11661                | "for_statement"
11662                | "while_statement"
11663                | "if_statement"
11664                | "class_specifier"
11665                | "struct_specifier"
11666                | "union_specifier"
11667        ) {
11668            return None;
11669        }
11670        current = parent.parent();
11671    }
11672    Some(enclosing_namespace_components(node, source))
11673}
11674
11675fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11676    let mut current = node.parent();
11677    while let Some(scope) = current {
11678        if matches!(
11679            scope.kind(),
11680            "compound_statement"
11681                | "declaration_list"
11682                | "field_declaration_list"
11683                | "translation_unit"
11684        ) {
11685            let mut depth = 0;
11686            let mut ancestor = scope.parent();
11687            while let Some(parent) = ancestor {
11688                depth += 1;
11689                ancestor = parent.parent();
11690            }
11691            return Some((
11692                scope.start_byte(),
11693                scope.end_byte(),
11694                depth,
11695                scope.kind() == "compound_statement",
11696            ));
11697        }
11698        current = scope.parent();
11699    }
11700    None
11701}
11702
11703/// Build the per-file structured using index for `file`.
11704///
11705/// The result is a pure function of the file's parsed content, which is what
11706/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11707/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11708/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11709pub fn build_source_using_index(
11710    cpp: &dyn CppSource,
11711    token: QueryToken<'_>,
11712    file: &ProjectFile,
11713) -> SourceUsingIndex {
11714    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11715        return SourceUsingIndex::default();
11716    };
11717    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11718}
11719
11720fn collect_source_using_index(
11721    cpp: &dyn CppSource,
11722    source_file: &ProjectFile,
11723    root: Node<'_>,
11724    source: &str,
11725) -> SourceUsingIndex {
11726    #[cfg(not(any(test, feature = "test-support")))]
11727    let _ = cpp;
11728    let mut index = SourceUsingIndex::default();
11729    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11730    let mut stack = vec![root];
11731    while let Some(node) = stack.pop() {
11732        let target = match node.kind() {
11733            "using_directive" | "using_declaration" => {
11734                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11735                    let mut namespace_components = Vec::new();
11736                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11737                        .map(|_| EffectiveUsingTarget::Namespace {
11738                            namespace_components,
11739                            global: is_globally_qualified_cpp_name(namespace_node),
11740                        })
11741                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11742                    let mut target_components = Vec::new();
11743                    (append_cpp_name_components(type_node, source, &mut target_components)
11744                        .is_some()
11745                        && target_components.len() >= 2)
11746                        .then(|| EffectiveUsingTarget::Ordinary {
11747                            name: target_components
11748                                .last()
11749                                .expect("ordinary using has a terminal component")
11750                                .clone(),
11751                            target_components,
11752                            global: is_globally_qualified_cpp_name(type_node),
11753                        })
11754                } else {
11755                    None
11756                }
11757            }
11758            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11759                |(type_node, global)| {
11760                    let mut target_components = Vec::new();
11761                    (append_cpp_name_components(type_node, source, &mut target_components)
11762                        .is_some()
11763                        && target_components.len() >= 2)
11764                        .then(|| EffectiveUsingTarget::Ordinary {
11765                            name: target_components
11766                                .last()
11767                                .expect("recovered ordinary using has a terminal component")
11768                                .clone(),
11769                            target_components,
11770                            global,
11771                        })
11772                },
11773            ),
11774            _ => None,
11775        };
11776        if let Some(target) = target {
11777            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11778            // searches from the root. The project index visits every AST node, but only these
11779            // structured using declarations need a guard environment. Keep the cheap target
11780            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11781            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11782            #[cfg(any(test, feature = "test-support"))]
11783            cpp.record_using_guard_context_inspection_for_test();
11784            let Some(required_guards) = binding_site_guards(node, source) else {
11785                let mut cursor = node.walk();
11786                stack.extend(node.children(&mut cursor));
11787                continue;
11788            };
11789            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11790                ordinary_using_scope(node)
11791            {
11792                let declaration_namespace = enclosing_namespace_components(node, source);
11793                let declaration_namespace = if declaration_namespace.is_empty() {
11794                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11795                        .unwrap_or(declaration_namespace)
11796                } else {
11797                    declaration_namespace
11798                };
11799                let namespace_scope = using_named_scope(node, source);
11800                let lexical_depth = declaration_namespace.len();
11801                let binding = OrdinaryTypeImport {
11802                    target,
11803                    source: source_file.clone(),
11804                    declaration_byte: node.end_byte(),
11805                    scope_start,
11806                    scope_end,
11807                    scope_depth,
11808                    block_scope,
11809                    lexical_depth,
11810                    declaration_namespace,
11811                    namespace_scope,
11812                    resolved_target_components: None,
11813                    required_guards,
11814                };
11815                match &binding.target {
11816                    EffectiveUsingTarget::Ordinary { name, .. } => index
11817                        .ordinary_by_name
11818                        .entry(name.clone())
11819                        .or_default()
11820                        .push(binding),
11821                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11822                }
11823            }
11824        }
11825        let mut cursor = node.walk();
11826        stack.extend(node.children(&mut cursor));
11827    }
11828    index
11829}
11830
11831struct OrphanedNamespaceEnvelope {
11832    body_end: usize,
11833    components: Vec<String>,
11834    class_names: HashSet<String>,
11835}
11836
11837/// Tree-sitter can terminate a namespace body at an object-like namespace
11838/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11839/// out-of-line definitions at translation-unit scope. A block-scoped using
11840/// declaration in one of those definitions still belongs to the namespace
11841/// selected by the malformed namespace envelope. Keep the envelope scan
11842/// source-local and reuse its structural ownership evidence for each using.
11843fn collect_orphaned_namespace_envelopes(
11844    root: Node<'_>,
11845    source: &str,
11846) -> Vec<OrphanedNamespaceEnvelope> {
11847    let mut envelopes = Vec::new();
11848    let mut stack = vec![root];
11849    while let Some(current) = stack.pop() {
11850        if current.kind() == "namespace_definition"
11851            && let Some(body) = current.child_by_field_name("body")
11852            && current.end_byte() == body.end_byte()
11853            && let Some(name) = current.child_by_field_name("name")
11854        {
11855            let mut components = enclosing_namespace_components(current, source);
11856            if append_cpp_name_components(name, source, &mut components).is_some()
11857                && !components.is_empty()
11858            {
11859                let mut class_names = HashSet::default();
11860                let mut body_stack = vec![body];
11861                while let Some(node) = body_stack.pop() {
11862                    if let Some(name) = orphaned_class_definition_name(node, source) {
11863                        class_names.insert(name);
11864                    }
11865                    let mut cursor = node.walk();
11866                    if node.kind() == "ERROR" {
11867                        body_stack.extend(node.children(&mut cursor));
11868                    } else {
11869                        body_stack.extend(node.named_children(&mut cursor));
11870                    }
11871                }
11872                envelopes.push(OrphanedNamespaceEnvelope {
11873                    body_end: body.end_byte(),
11874                    components,
11875                    class_names,
11876                });
11877            }
11878        }
11879        let mut cursor = current.walk();
11880        stack.extend(current.named_children(&mut cursor));
11881    }
11882    envelopes
11883}
11884
11885fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11886    if matches!(
11887        node.kind(),
11888        "class_specifier" | "struct_specifier" | "union_specifier"
11889    ) {
11890        let body = node.child_by_field_name("body")?;
11891        let name = node.child_by_field_name("name")?;
11892        return (!name.is_missing() && !body.is_missing())
11893            .then(|| node_text(name, source).to_string());
11894    }
11895    if node.kind() != "ERROR" {
11896        return None;
11897    }
11898
11899    // When an object-like namespace macro is parsed as a function definition,
11900    // tree-sitter can place the entire class declaration inside an ERROR node
11901    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11902    // fallback structural: accept only a named class-like keyword followed by
11903    // a real body, never an arbitrary identifier mentioned in the envelope.
11904    for index in 0..node.child_count() {
11905        let Some(keyword) = node.child(index) else {
11906            continue;
11907        };
11908        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11909            continue;
11910        }
11911        let mut name = None;
11912        for next_index in (index + 1)..node.child_count() {
11913            let Some(next) = node.child(next_index) else {
11914                continue;
11915            };
11916            if next.kind() == ";" {
11917                break;
11918            }
11919            if next.kind() == "{" {
11920                return name
11921                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11922                    .map(|name_node| node_text(name_node, source).to_string());
11923            }
11924            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11925                name = Some(next);
11926            }
11927        }
11928    }
11929    None
11930}
11931
11932fn recovered_orphaned_namespace_components(
11933    node: Node<'_>,
11934    source: &str,
11935    envelopes: &[OrphanedNamespaceEnvelope],
11936) -> Option<Vec<String>> {
11937    let owner_name = orphaned_using_owner_name(node, source)?;
11938    envelopes
11939        .iter()
11940        .filter(|envelope| {
11941            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11942        })
11943        .max_by_key(|envelope| envelope.body_end)
11944        .map(|envelope| envelope.components.clone())
11945}
11946
11947fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11948    let function = std::iter::successors(node.parent(), |current| current.parent())
11949        .find(|current| current.kind() == "function_definition")?;
11950    let owner = function_definition_owner_lookup_node(function)?;
11951    let scope = owner.child_by_field_name("scope")?;
11952    let mut components = Vec::new();
11953    append_cpp_name_components(scope, source, &mut components)?;
11954    // A qualified out-of-line member definition already carries its namespace
11955    // in the declarator scope (for example `foo::Widget::run`). The recovery
11956    // path is only for parser-orphaned top-level members whose owner scope
11957    // collapsed to the bare class name; requiring that shape prevents an
11958    // earlier, unrelated namespace/class from leaking into a global function's
11959    // using-directive lookup.
11960    if components.len() != 1 {
11961        return None;
11962    }
11963    components.pop()
11964}
11965
11966fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11967    let started = Instant::now();
11968    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11969    let source_files = visibility.all_visible_source_files();
11970    if report_stats {
11971        eprintln!(
11972            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11973            source_files.len()
11974        );
11975    }
11976    let mut project = ProjectUsingIndex::default();
11977    let mut ordinary_bindings = 0usize;
11978    for source_file in &source_files {
11979        // The per-file index is memoized on the analyzer, so assembling the
11980        // project index for a fresh `VisibilityIndex` copies bindings instead
11981        // of re-walking each file's AST (#1927).
11982        let source_index = visibility
11983            .cpp()
11984            .source_using_index(visibility.token(), source_file);
11985        for (name, bindings) in &source_index.ordinary_by_name {
11986            ordinary_bindings += bindings.len();
11987            project
11988                .ordinary_by_name
11989                .entry(name.clone())
11990                .or_default()
11991                .extend(bindings.iter().cloned());
11992        }
11993        project
11994            .directives
11995            .extend(source_index.directives.iter().cloned());
11996    }
11997    if report_stats {
11998        eprintln!(
11999            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
12000            source_files.len(),
12001            project.ordinary_by_name.len(),
12002            ordinary_bindings,
12003            project.directives.len(),
12004            started.elapsed().as_millis(),
12005        );
12006    }
12007    project
12008}
12009
12010fn project_using_index(visibility: &VisibilityIndex<'_>) -> Arc<ProjectUsingIndex> {
12011    visibility.project_using_index(|| build_project_using_index(visibility))
12012}
12013
12014/// Build the immutable project-wide using index before a parallel file scan.
12015///
12016/// Keeping the `OnceLock` publication here avoids making every scanner carry
12017/// an eager index, while callers that are about to fan out can prevent one
12018/// worker from doing the whole build as its peers wait on the lock.
12019pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
12020    let _ = project_using_index(visibility);
12021}
12022
12023fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
12024    let (components, global) = match &binding.target {
12025        EffectiveUsingTarget::Ordinary {
12026            target_components,
12027            global,
12028            ..
12029        } => (target_components, *global),
12030        EffectiveUsingTarget::Namespace {
12031            namespace_components,
12032            global,
12033        } => (namespace_components, *global),
12034    };
12035    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
12036}
12037
12038fn using_binding_target_components_for_name(
12039    binding: &OrdinaryTypeImport,
12040    project: &ProjectUsingIndex,
12041    visibility: &VisibilityIndex<'_>,
12042    file: &ProjectFile,
12043    name: &str,
12044) -> Option<Vec<String>> {
12045    // Built once per call rather than per candidate: the filter runs over every
12046    // visible identifier of `name`, and the source is the same object each time.
12047    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
12048    let visible_candidates = visibility
12049        .visible_identifier_candidates(file, name)
12050        .filter(|candidate| {
12051            candidate.is_class()
12052                || declared_type_alias(&cpp_source, candidate)
12053                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
12054        })
12055        .collect::<Vec<_>>();
12056    if visible_candidates.is_empty() {
12057        return None;
12058    }
12059    match &binding.target {
12060        EffectiveUsingTarget::Ordinary {
12061            name: imported_name,
12062            ..
12063        } if imported_name == name => {
12064            effective_using_target_tiers(binding)
12065                .into_iter()
12066                .find(|qualified| {
12067                    let qualified_name = qualified.join("::");
12068                    visible_candidates
12069                        .iter()
12070                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
12071                })
12072        }
12073        EffectiveUsingTarget::Namespace { .. } => {
12074            visibility.note_using_namespace_lookup_for_test();
12075            let target_tiers = effective_using_target_tiers(binding);
12076            let resolved = target_tiers
12077                .iter()
12078                .find(|namespace_components| {
12079                    let namespace = namespace_components.join("::");
12080                    visible_candidates.iter().any(|candidate| {
12081                        visibility.note_using_name_candidate_inspection_for_test();
12082                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
12083                            candidate_namespace == namespace
12084                                || candidate_namespace.starts_with(&format!("{namespace}::"))
12085                        })
12086                    }) || project.directives.iter().any(|candidate| {
12087                        candidate.namespace_scope.as_deref()
12088                            == Some(namespace_components.as_slice())
12089                    }) || project
12090                        .ordinary_by_name
12091                        .values()
12092                        .flatten()
12093                        .any(|candidate| {
12094                            candidate.namespace_scope.as_deref()
12095                                == Some(namespace_components.as_slice())
12096                        })
12097                })
12098                .cloned();
12099            resolved.or_else(|| {
12100                // A sole lexical namespace tier is itself enough to retain the
12101                // directive. Candidate identity is resolved later, where
12102                // target guidance and macro-expanded owner names are available.
12103                // Dropping it here makes an unrelated same-terminal type hide
12104                // the actual namespace member before lookup can compare owners.
12105                (target_tiers.len() == 1)
12106                    .then(|| target_tiers.into_iter().next())
12107                    .flatten()
12108            })
12109        }
12110        EffectiveUsingTarget::Ordinary { .. } => None,
12111    }
12112}
12113
12114fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
12115    let start = activation.checked_sub(1)?;
12116    let mut node = root.descendant_for_byte_range(start, activation)?;
12117    while node.kind() != "preproc_include" {
12118        node = node.parent()?;
12119    }
12120    Some(node)
12121}
12122
12123fn project_using_bindings(
12124    binding: OrdinaryTypeImport,
12125    visibility: &VisibilityIndex<'_>,
12126    file: &ProjectFile,
12127    root: Node<'_>,
12128    source: &str,
12129) -> Vec<OrdinaryTypeImport> {
12130    if binding.source == *file {
12131        return vec![binding];
12132    }
12133    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
12134        return Vec::new();
12135    }
12136    visibility.note_using_donor_activation_for_test();
12137    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
12138        return Vec::new();
12139    };
12140    let projections = visibility
12141        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
12142        .map_or_else(
12143            || {
12144                visibility.conditional_include_projections_for_source(
12145                    file,
12146                    prepared.as_ref(),
12147                    &binding.source,
12148                )
12149            },
12150            |activation_byte| {
12151                Arc::from([ConditionalIncludeProjection {
12152                    activation_byte,
12153                    required_guards: HashSet::default(),
12154                    partial_guards: HashSet::default(),
12155                }])
12156            },
12157        );
12158    projections
12159        .iter()
12160        .cloned()
12161        .filter_map(|projection| {
12162            let required_guards =
12163                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
12164            let mut projected = binding.clone();
12165            projected.required_guards = required_guards;
12166            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
12167        })
12168        .collect()
12169}
12170
12171fn project_using_binding_at_activation(
12172    mut binding: OrdinaryTypeImport,
12173    activation: usize,
12174    root: Node<'_>,
12175    source: &str,
12176) -> Option<OrdinaryTypeImport> {
12177    let include = include_node_for_activation(root, activation)?;
12178    let include_namespace = enclosing_namespace_components(include, source);
12179    let mut declaration_namespace = include_namespace.clone();
12180    declaration_namespace.extend(binding.declaration_namespace);
12181    binding.declaration_namespace = declaration_namespace;
12182    binding.declaration_byte = activation;
12183    if let Some(prefix) = using_named_scope(include, source) {
12184        let mut projected = prefix;
12185        projected.extend(binding.namespace_scope.take().unwrap_or_default());
12186        binding.scope_depth = projected.len();
12187        binding.block_scope = false;
12188        binding.lexical_depth = projected.len();
12189        binding.namespace_scope = Some(projected);
12190        binding.scope_start = 0;
12191        binding.scope_end = usize::MAX;
12192        Some(binding)
12193    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
12194        binding.namespace_scope = None;
12195        binding.scope_start = start;
12196        binding.scope_end = end;
12197        binding.scope_depth = depth;
12198        binding.block_scope = block_scope;
12199        binding.lexical_depth = include_namespace.len();
12200        Some(binding)
12201    } else {
12202        None
12203    }
12204}
12205
12206/// `node` may be any node of `file`'s tree; the projection reaches the tree
12207/// root itself when it needs one. `Node::parent` re-descends from the root on
12208/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
12209/// call site cost a near-full-AST scan per reference on a large flat file
12210/// (#1927); a name with no candidate bindings never pays for it.
12211pub fn effective_using_bindings_for_name(
12212    visibility: &VisibilityIndex<'_>,
12213    imports: &OrdinaryTypeImportCell,
12214    file: &ProjectFile,
12215    node: Node<'_>,
12216    source: &str,
12217    name: &str,
12218) -> Arc<[OrdinaryTypeImport]> {
12219    imports
12220        .projection_cell(name)
12221        .get_or_init(|| {
12222            let retained_project = project_using_index(visibility);
12223            let project = retained_project.as_ref();
12224            let name_bindings = project.ordinary_by_name.get(name);
12225            if name_bindings.is_none() && project.directives.is_empty() {
12226                return Arc::from(Vec::new());
12227            }
12228            let root = root_node(node);
12229            let mut projected = Vec::new();
12230            for binding in name_bindings
12231                .into_iter()
12232                .flatten()
12233                .chain(project.directives.iter())
12234            {
12235                if !visibility.source_is_visible(file, &binding.source) {
12236                    continue;
12237                }
12238                let target_components = using_binding_target_components_for_name(
12239                    binding, project, visibility, file, name,
12240                )
12241                .or_else(|| match &binding.target {
12242                    EffectiveUsingTarget::Ordinary {
12243                        name: imported_name,
12244                        target_components,
12245                        ..
12246                    } if imported_name == name => Some(target_components.clone()),
12247                    EffectiveUsingTarget::Ordinary { .. }
12248                    | EffectiveUsingTarget::Namespace { .. } => None,
12249                });
12250                let Some(target_components) = target_components else {
12251                    continue;
12252                };
12253                let mut binding = binding.clone();
12254                binding.resolved_target_components = Some(target_components);
12255                projected.extend(project_using_bindings(
12256                    binding, visibility, file, root, source,
12257                ));
12258            }
12259            Arc::from(projected)
12260        })
12261        .clone()
12262}
12263
12264pub fn initialized_ordinary_type_imports(
12265    root: Node<'_>,
12266    analyzer: &CppGraphSource<'_>,
12267    visibility: &VisibilityIndex<'_>,
12268    file: &ProjectFile,
12269    source: &str,
12270) -> OrdinaryTypeImportCell {
12271    let cell = visibility.ordinary_type_import_cell(file);
12272    let _ = (root, analyzer, source);
12273    cell
12274}
12275
12276/// `reference_guards` is the reference node's guard environment, computed once
12277/// by the caller and shared across every binding: recomputing it per binding
12278/// repeated a full ancestor climb whose every `Node::parent` step re-descends
12279/// from the root (#1927).
12280fn effective_using_binding_active(
12281    binding: &OrdinaryTypeImport,
12282    node: Node<'_>,
12283    lexical_scope: &[String],
12284    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12285    visibility: &VisibilityIndex<'_>,
12286    file: &ProjectFile,
12287) -> bool {
12288    effective_using_binding_guards_active(
12289        binding,
12290        node.start_byte(),
12291        reference_guards,
12292        visibility,
12293        file,
12294    ) && binding.namespace_scope.as_ref().map_or_else(
12295        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12296        |namespace| lexical_scope.starts_with(namespace),
12297    )
12298}
12299
12300fn effective_using_binding_guards_active(
12301    binding: &OrdinaryTypeImport,
12302    reference_byte: usize,
12303    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12304    visibility: &VisibilityIndex<'_>,
12305    file: &ProjectFile,
12306) -> bool {
12307    binding.declaration_byte <= reference_byte
12308        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
12309        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12310}
12311
12312fn effective_using_binding_guards_compatible(
12313    binding: &OrdinaryTypeImport,
12314    reference_byte: usize,
12315    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12316    visibility: &VisibilityIndex<'_>,
12317    file: &ProjectFile,
12318) -> bool {
12319    binding.source != *file
12320        && !binding.required_guards.is_empty()
12321        && binding.declaration_byte <= reference_byte
12322        && reference_guards.is_some_and(|active| {
12323            !binding.required_guards.is_subset(active)
12324                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
12325        })
12326        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12327}
12328
12329fn effective_using_binding_guards_stable_between(
12330    binding: &OrdinaryTypeImport,
12331    reference_byte: usize,
12332    visibility: &VisibilityIndex<'_>,
12333    file: &ProjectFile,
12334) -> bool {
12335    visibility.preprocessor_guards_stable_between(
12336        file,
12337        binding.declaration_byte,
12338        reference_byte,
12339        &binding.required_guards,
12340    )
12341}
12342
12343#[allow(clippy::too_many_arguments)]
12344fn binding_type_candidates(
12345    binding: &OrdinaryTypeImport,
12346    active_bindings: &[&OrdinaryTypeImport],
12347    analyzer: &CppGraphSource<'_>,
12348    visibility: &VisibilityIndex<'_>,
12349    file: &ProjectFile,
12350    name: &str,
12351    direct_target: Option<&CodeUnit>,
12352    reference_byte: usize,
12353) -> Vec<(CodeUnit, Vec<String>)> {
12354    ordinary_binding_lookup_targets(binding, active_bindings, name)
12355        .into_iter()
12356        .flat_map(|target| {
12357            let mut candidates = visibility
12358                .visible_identifier_candidates(file, name)
12359                .filter(|candidate| {
12360                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
12361                        && type_candidate_matches_lookup_components(
12362                            analyzer,
12363                            visibility,
12364                            file,
12365                            candidate,
12366                            reference_byte,
12367                            &target,
12368                        )
12369                })
12370                .cloned()
12371                .collect::<Vec<_>>();
12372            if candidates.is_empty()
12373                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12374                && let Some(target_unit) = direct_target
12375            {
12376                let expanded_target_name = macro_expanded_cpp_name_components(
12377                    visibility,
12378                    file,
12379                    target_unit,
12380                    reference_byte,
12381                );
12382                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
12383                    && expanded_target_name == target
12384                    && visibility.external_type_candidate_visible_at(
12385                        file,
12386                        target_unit,
12387                        reference_byte,
12388                    )
12389                {
12390                    candidates.push(target_unit.clone());
12391                }
12392            }
12393            if candidates.is_empty()
12394                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12395                && let Some(target_unit) = direct_target
12396            {
12397                let visible_types = visibility
12398                    .visible_identifier_candidates(file, name)
12399                    .filter(|candidate| {
12400                        candidate.is_class() || declared_type_alias(analyzer, candidate)
12401                    })
12402                    .collect::<Vec<_>>();
12403                let uniquely_names_target = !visible_types.is_empty()
12404                    && visible_types
12405                        .iter()
12406                        .all(|candidate| same_visible_symbol(candidate, target_unit));
12407                if uniquely_names_target {
12408                    candidates.extend(visible_types.into_iter().cloned());
12409                }
12410            }
12411            candidates
12412                .into_iter()
12413                .map(move |candidate| (candidate, target.clone()))
12414        })
12415        .collect()
12416}
12417
12418fn macro_expanded_cpp_name_components(
12419    visibility: &VisibilityIndex<'_>,
12420    file: &ProjectFile,
12421    unit: &CodeUnit,
12422    reference_byte: usize,
12423) -> Vec<String> {
12424    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12425        brokk_bifrost_core::analyzer::Language::Cpp,
12426        &cpp_name_for(unit),
12427    )
12428    .into_iter()
12429    .flat_map(|component| {
12430        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
12431    })
12432    .collect()
12433}
12434
12435fn macro_expanded_cpp_name_component(
12436    visibility: &VisibilityIndex<'_>,
12437    file: &ProjectFile,
12438    component: String,
12439    reference_byte: usize,
12440) -> Vec<String> {
12441    let Some(replacement) =
12442        visibility.object_macro_replacement_at(file, &component, reference_byte)
12443    else {
12444        return vec![component];
12445    };
12446    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12447        brokk_bifrost_core::analyzer::Language::Cpp,
12448        &replacement,
12449    );
12450    if expanded.is_empty() {
12451        vec![component]
12452    } else {
12453        expanded
12454    }
12455}
12456
12457/// Whether a visible type has the requested qualified spelling.
12458///
12459/// Members of an inline namespace are also members of its enclosing namespace,
12460/// so an ordinary using-declaration may legally omit the inline component. The
12461/// stored FQ name retains that component to keep declarations distinct. Recover
12462/// the omitted spellings from the candidate declaration's namespace ancestors,
12463/// using CST `inline` tokens rather than guessing from names.
12464fn type_candidate_matches_lookup_components(
12465    analyzer: &CppGraphSource<'_>,
12466    visibility: &VisibilityIndex<'_>,
12467    file: &ProjectFile,
12468    candidate: &CodeUnit,
12469    reference_byte: usize,
12470    target: &[String],
12471) -> bool {
12472    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
12473    if expanded == target {
12474        return true;
12475    }
12476    let Some(cpp) = analyzer.cpp else {
12477        return false;
12478    };
12479    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
12480        return false;
12481    };
12482    let root = prepared.tree().root_node();
12483    for range in analyzer.ranges(candidate) {
12484        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
12485        else {
12486            continue;
12487        };
12488        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
12489        loop {
12490            if current.kind() == "namespace_definition"
12491                && let Some(name) = current.child_by_field_name("name")
12492            {
12493                let mut components = Vec::new();
12494                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
12495                    && !components.is_empty()
12496                {
12497                    let inline = (0..current.child_count())
12498                        .filter_map(|index| current.child(index))
12499                        .any(|child| !child.is_named() && child.kind() == "inline");
12500                    namespaces.push((components, inline));
12501                }
12502            }
12503            let Some(parent) = current.parent() else {
12504                break;
12505            };
12506            current = parent;
12507        }
12508        namespaces.reverse();
12509        let mut namespace_components = Vec::new();
12510        let mut inline_indexes = HashSet::default();
12511        for (components, inline) in namespaces {
12512            for component in components {
12513                let expanded_component =
12514                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
12515                if inline {
12516                    inline_indexes.extend(
12517                        namespace_components.len()
12518                            ..namespace_components.len() + expanded_component.len(),
12519                    );
12520                }
12521                namespace_components.extend(expanded_component);
12522            }
12523        }
12524        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
12525            continue;
12526        }
12527        // Each inline namespace component can be present or elided. Compare
12528        // those alternatives as a small dynamic program instead of generating
12529        // every subset of a deeply nested inline-namespace chain.
12530        let mut reachable = vec![false; target.len() + 1];
12531        reachable[0] = true;
12532        for (index, component) in expanded.iter().enumerate() {
12533            let mut next = vec![false; target.len() + 1];
12534            for (target_index, reached) in reachable.iter().copied().enumerate() {
12535                if !reached {
12536                    continue;
12537                }
12538                if inline_indexes.contains(&index) {
12539                    next[target_index] = true;
12540                }
12541                if target
12542                    .get(target_index)
12543                    .is_some_and(|target_component| target_component == component)
12544                {
12545                    next[target_index + 1] = true;
12546                }
12547            }
12548            reachable = next;
12549        }
12550        if reachable[target.len()] {
12551            return true;
12552        }
12553    }
12554    false
12555}
12556
12557#[allow(clippy::too_many_arguments)]
12558fn resolved_type_import(
12559    candidates: Vec<(CodeUnit, Vec<String>)>,
12560    lexical_depth: usize,
12561    is_direct: bool,
12562    analyzer: &CppGraphSource<'_>,
12563    visibility: &VisibilityIndex<'_>,
12564    file: &ProjectFile,
12565    direct_target: Option<&CodeUnit>,
12566) -> OrdinaryTypeImportResolution {
12567    let logical = deduplicate_type_import_candidates(candidates);
12568    let selected = match logical.as_slice() {
12569        [] => return OrdinaryTypeImportResolution::Missing,
12570        [only] => only,
12571        // Several declarations of one FQN in one file are configuration
12572        // spellings of one entity, not competing types (#1845): the imported
12573        // name is unambiguous, only the branch that supplies it depends on the
12574        // build.
12575        several => {
12576            let units = several
12577                .iter()
12578                .map(|(unit, _)| unit)
12579                .collect::<Vec<&CodeUnit>>();
12580            let Some(spelling) = direct_target.and_then(|target| {
12581                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
12582            }) else {
12583                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
12584            };
12585            several
12586                .iter()
12587                .find(|(unit, _)| same_symbol(unit, spelling))
12588                .expect("the selected spelling is one of the imported candidates")
12589        }
12590    };
12591    OrdinaryTypeImportResolution::Resolved {
12592        target: selected.0.clone(),
12593        target_components: selected.1.clone(),
12594        lexical_depth,
12595        is_direct,
12596    }
12597}
12598
12599#[allow(clippy::too_many_arguments)]
12600fn ordinary_type_import_resolution(
12601    node: Node<'_>,
12602    components: &[String],
12603    global: bool,
12604    analyzer: &CppGraphSource<'_>,
12605    visibility: &VisibilityIndex<'_>,
12606    imports: &OrdinaryTypeImportCell,
12607    file: &ProjectFile,
12608    source: &str,
12609    lexical_scope: &[String],
12610    direct_target: Option<&CodeUnit>,
12611) -> OrdinaryTypeImportResolution {
12612    if global || components.len() != 1 {
12613        return OrdinaryTypeImportResolution::Missing;
12614    }
12615    let name = &components[0];
12616    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12617    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
12618    // step re-descends from the root (#1927). A name with no bindings needs
12619    // none of it, and one environment serves every binding of the reference.
12620    if bindings.is_empty() {
12621        return OrdinaryTypeImportResolution::Missing;
12622    }
12623    let reference_guards = preprocessor_guard_environment(node, source);
12624    let active = bindings
12625        .iter()
12626        .filter(|binding| {
12627            effective_using_binding_active(
12628                binding,
12629                node,
12630                lexical_scope,
12631                reference_guards.as_ref(),
12632                visibility,
12633                file,
12634            )
12635        })
12636        .collect::<Vec<_>>();
12637    let transitive = bindings
12638        .iter()
12639        .filter(|binding| {
12640            effective_using_binding_guards_active(
12641                binding,
12642                node.start_byte(),
12643                reference_guards.as_ref(),
12644                visibility,
12645                file,
12646            ) && (binding.namespace_scope.is_some()
12647                || (binding.scope_start <= node.start_byte()
12648                    && node.end_byte() <= binding.scope_end))
12649        })
12650        .collect::<Vec<_>>();
12651    ordinary_type_import_resolution_for_bindings(
12652        node,
12653        name,
12654        analyzer,
12655        visibility,
12656        file,
12657        lexical_scope,
12658        direct_target,
12659        &active,
12660        &transitive,
12661    )
12662}
12663
12664#[allow(clippy::too_many_arguments)]
12665fn compatible_foreign_type_import_resolution(
12666    node: Node<'_>,
12667    components: &[String],
12668    global: bool,
12669    analyzer: &CppGraphSource<'_>,
12670    visibility: &VisibilityIndex<'_>,
12671    imports: &OrdinaryTypeImportCell,
12672    file: &ProjectFile,
12673    source: &str,
12674    lexical_scope: &[String],
12675    direct_target: Option<&CodeUnit>,
12676) -> OrdinaryTypeImportResolution {
12677    if global || components.len() != 1 {
12678        return OrdinaryTypeImportResolution::Missing;
12679    }
12680    let name = &components[0];
12681    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12682    if bindings.is_empty() {
12683        return OrdinaryTypeImportResolution::Missing;
12684    }
12685    let reference_guards = preprocessor_guard_environment(node, source);
12686    let compatible = bindings
12687        .iter()
12688        .filter(|binding| {
12689            effective_using_binding_guards_compatible(
12690                binding,
12691                node.start_byte(),
12692                reference_guards.as_ref(),
12693                visibility,
12694                file,
12695            ) && binding.namespace_scope.as_ref().map_or_else(
12696                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12697                |namespace| lexical_scope.starts_with(namespace),
12698            )
12699        })
12700        .collect::<Vec<_>>();
12701    let transitive = bindings
12702        .iter()
12703        .filter(|binding| {
12704            effective_using_binding_guards_compatible(
12705                binding,
12706                node.start_byte(),
12707                reference_guards.as_ref(),
12708                visibility,
12709                file,
12710            ) && (binding.namespace_scope.is_some()
12711                || (binding.scope_start <= node.start_byte()
12712                    && node.end_byte() <= binding.scope_end))
12713        })
12714        .collect::<Vec<_>>();
12715    ordinary_type_import_resolution_for_bindings(
12716        node,
12717        name,
12718        analyzer,
12719        visibility,
12720        file,
12721        lexical_scope,
12722        direct_target,
12723        &compatible,
12724        &transitive,
12725    )
12726}
12727
12728#[allow(clippy::too_many_arguments)]
12729fn ordinary_type_import_resolution_for_bindings(
12730    node: Node<'_>,
12731    name: &str,
12732    analyzer: &CppGraphSource<'_>,
12733    visibility: &VisibilityIndex<'_>,
12734    file: &ProjectFile,
12735    lexical_scope: &[String],
12736    direct_target: Option<&CodeUnit>,
12737    active: &[&OrdinaryTypeImport],
12738    transitive: &[&OrdinaryTypeImport],
12739) -> OrdinaryTypeImportResolution {
12740    let mut concrete_depths = active
12741        .iter()
12742        .filter(|binding| binding.namespace_scope.is_none())
12743        .map(|binding| binding.scope_depth)
12744        .collect::<Vec<_>>();
12745    concrete_depths.sort_unstable();
12746    concrete_depths.dedup();
12747    for depth in concrete_depths.into_iter().rev() {
12748        let at_tier = active
12749            .iter()
12750            .copied()
12751            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12752        let direct = at_tier
12753            .clone()
12754            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12755            .flat_map(|binding| {
12756                binding_type_candidates(
12757                    binding,
12758                    transitive,
12759                    analyzer,
12760                    visibility,
12761                    file,
12762                    name,
12763                    direct_target,
12764                    node.start_byte(),
12765                )
12766            })
12767            .collect::<Vec<_>>();
12768        if !direct.is_empty() {
12769            return resolved_type_import(
12770                direct,
12771                lexical_scope.len(),
12772                true,
12773                analyzer,
12774                visibility,
12775                file,
12776                direct_target,
12777            );
12778        }
12779        let directives = at_tier
12780            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12781            .flat_map(|binding| {
12782                binding_type_candidates(
12783                    binding,
12784                    transitive,
12785                    analyzer,
12786                    visibility,
12787                    file,
12788                    name,
12789                    direct_target,
12790                    node.start_byte(),
12791                )
12792            })
12793            .collect::<Vec<_>>();
12794        if !directives.is_empty() {
12795            return resolved_type_import(
12796                directives,
12797                lexical_scope.len(),
12798                false,
12799                analyzer,
12800                visibility,
12801                file,
12802                direct_target,
12803            );
12804        }
12805    }
12806    for prefix_len in (0..=lexical_scope.len()).rev() {
12807        let tier = &lexical_scope[..prefix_len];
12808        let at_tier = active
12809            .iter()
12810            .copied()
12811            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12812        let direct = at_tier
12813            .clone()
12814            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12815            .flat_map(|binding| {
12816                binding_type_candidates(
12817                    binding,
12818                    transitive,
12819                    analyzer,
12820                    visibility,
12821                    file,
12822                    name,
12823                    direct_target,
12824                    node.start_byte(),
12825                )
12826            })
12827            .collect::<Vec<_>>();
12828        if !direct.is_empty() {
12829            return resolved_type_import(
12830                direct,
12831                prefix_len,
12832                true,
12833                analyzer,
12834                visibility,
12835                file,
12836                direct_target,
12837            );
12838        }
12839        let directives = at_tier
12840            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12841            .flat_map(|binding| {
12842                binding_type_candidates(
12843                    binding,
12844                    transitive,
12845                    analyzer,
12846                    visibility,
12847                    file,
12848                    name,
12849                    direct_target,
12850                    node.start_byte(),
12851                )
12852            })
12853            .collect::<Vec<_>>();
12854        if !directives.is_empty() {
12855            return resolved_type_import(
12856                directives,
12857                prefix_len,
12858                false,
12859                analyzer,
12860                visibility,
12861                file,
12862                direct_target,
12863            );
12864        }
12865    }
12866    OrdinaryTypeImportResolution::Missing
12867}
12868
12869#[allow(clippy::too_many_arguments)]
12870pub fn resolve_type_components_lexically_at(
12871    node: Node<'_>,
12872    components: &[String],
12873    global: bool,
12874    analyzer: &CppGraphSource<'_>,
12875    visibility: &VisibilityIndex<'_>,
12876    ordinary_type_imports: &OrdinaryTypeImportCell,
12877    file: &ProjectFile,
12878    source: &str,
12879) -> LexicalTypeResolution {
12880    resolve_type_components_lexically_at_inner(
12881        node,
12882        components,
12883        global,
12884        analyzer,
12885        visibility,
12886        ordinary_type_imports,
12887        file,
12888        source,
12889        None,
12890        false,
12891        false,
12892        false,
12893        None,
12894    )
12895}
12896
12897/// Resolve a type at its lexical reference site while retaining the identity
12898/// of an alias that C++ lookup selects.
12899///
12900/// Forward navigation uses the selected spelling as its destination, whereas
12901/// graph attribution normally canonicalizes an alias to its target. Both
12902/// surfaces must still apply the same ordinary using-declarations, declaration
12903/// order, guard state, and lexical-depth precedence.
12904#[allow(clippy::too_many_arguments)]
12905pub fn resolve_type_components_lexically_at_preserving_alias(
12906    node: Node<'_>,
12907    components: &[String],
12908    global: bool,
12909    analyzer: &CppGraphSource<'_>,
12910    visibility: &VisibilityIndex<'_>,
12911    file: &ProjectFile,
12912    source: &str,
12913) -> LexicalTypeResolution {
12914    let ordinary_type_imports =
12915        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12916    resolve_type_components_lexically_at_inner(
12917        node,
12918        components,
12919        global,
12920        analyzer,
12921        visibility,
12922        &ordinary_type_imports,
12923        file,
12924        source,
12925        None,
12926        false,
12927        true,
12928        true,
12929        None,
12930    )
12931}
12932
12933#[allow(clippy::too_many_arguments)]
12934fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12935    node: Node<'_>,
12936    components: &[String],
12937    global: bool,
12938    analyzer: &CppGraphSource<'_>,
12939    visibility: &VisibilityIndex<'_>,
12940    ordinary_type_imports: &OrdinaryTypeImportCell,
12941    file: &ProjectFile,
12942    source: &str,
12943    scope_cache: Option<&LexicalScopeCache>,
12944) -> LexicalTypeResolution {
12945    resolve_type_components_lexically_at_inner(
12946        node,
12947        components,
12948        global,
12949        analyzer,
12950        visibility,
12951        ordinary_type_imports,
12952        file,
12953        source,
12954        None,
12955        false,
12956        true,
12957        false,
12958        scope_cache,
12959    )
12960}
12961
12962#[allow(clippy::too_many_arguments)]
12963fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12964    node: Node<'_>,
12965    components: &[String],
12966    global: bool,
12967    analyzer: &CppGraphSource<'_>,
12968    visibility: &VisibilityIndex<'_>,
12969    ordinary_type_imports: &OrdinaryTypeImportCell,
12970    file: &ProjectFile,
12971    source: &str,
12972    target: &CodeUnit,
12973    apply_structured_prefilter: bool,
12974    scope_cache: Option<&LexicalScopeCache>,
12975) -> LexicalTypeResolution {
12976    resolve_type_components_lexically_at_inner(
12977        node,
12978        components,
12979        global,
12980        analyzer,
12981        visibility,
12982        ordinary_type_imports,
12983        file,
12984        source,
12985        Some(target),
12986        apply_structured_prefilter,
12987        false,
12988        false,
12989        scope_cache,
12990    )
12991}
12992
12993#[allow(clippy::too_many_arguments)]
12994fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12995    node: Node<'_>,
12996    components: &[String],
12997    global: bool,
12998    analyzer: &CppGraphSource<'_>,
12999    visibility: &VisibilityIndex<'_>,
13000    ordinary_type_imports: &OrdinaryTypeImportCell,
13001    file: &ProjectFile,
13002    source: &str,
13003    recovered_scope: &[String],
13004) -> LexicalTypeResolution {
13005    resolve_type_components_in_authoritative_scope(
13006        node,
13007        components,
13008        global,
13009        analyzer,
13010        visibility,
13011        ordinary_type_imports,
13012        file,
13013        source,
13014        None,
13015        false,
13016        true,
13017        false,
13018        recovered_scope.to_vec(),
13019    )
13020}
13021
13022#[allow(clippy::too_many_arguments)]
13023fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
13024    node: Node<'_>,
13025    components: &[String],
13026    global: bool,
13027    analyzer: &CppGraphSource<'_>,
13028    visibility: &VisibilityIndex<'_>,
13029    ordinary_type_imports: &OrdinaryTypeImportCell,
13030    file: &ProjectFile,
13031    source: &str,
13032    target: &CodeUnit,
13033    apply_structured_prefilter: bool,
13034    recovered_scope: &[String],
13035) -> LexicalTypeResolution {
13036    resolve_type_components_in_authoritative_scope(
13037        node,
13038        components,
13039        global,
13040        analyzer,
13041        visibility,
13042        ordinary_type_imports,
13043        file,
13044        source,
13045        Some(target),
13046        apply_structured_prefilter,
13047        false,
13048        false,
13049        recovered_scope.to_vec(),
13050    )
13051}
13052
13053#[allow(clippy::too_many_arguments)]
13054fn resolve_type_components_lexically_at_inner(
13055    node: Node<'_>,
13056    components: &[String],
13057    global: bool,
13058    analyzer: &CppGraphSource<'_>,
13059    visibility: &VisibilityIndex<'_>,
13060    ordinary_type_imports: &OrdinaryTypeImportCell,
13061    file: &ProjectFile,
13062    source: &str,
13063    direct_target: Option<&CodeUnit>,
13064    apply_structured_prefilter: bool,
13065    preserve_alias: bool,
13066    allow_compatible_foreign_import: bool,
13067    scope_cache: Option<&LexicalScopeCache>,
13068) -> LexicalTypeResolution {
13069    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13070    let lexical_scope_started = Instant::now();
13071    if report_stats {
13072        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
13073    }
13074    let lexical_scope = if global {
13075        Vec::new()
13076    } else {
13077        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
13078            node,
13079            analyzer,
13080            visibility,
13081            file,
13082            source,
13083            true,
13084            recovered_macro_decorated_declarator_type(node)
13085                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
13086            scope_cache,
13087        ) {
13088            LexicalScopeResolution::Resolved(scope) => scope,
13089            LexicalScopeResolution::Ambiguous => {
13090                if report_stats {
13091                    eprintln!(
13092                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
13093                        lexical_scope_started.elapsed().as_millis(),
13094                    );
13095                }
13096                return LexicalTypeResolution::Ambiguous;
13097            }
13098            LexicalScopeResolution::Missing => {
13099                if report_stats {
13100                    eprintln!(
13101                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
13102                        lexical_scope_started.elapsed().as_millis(),
13103                    );
13104                }
13105                return LexicalTypeResolution::Missing;
13106            }
13107        }
13108    };
13109    if report_stats {
13110        eprintln!(
13111            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
13112            lexical_scope.len(),
13113            lexical_scope_started.elapsed().as_millis(),
13114        );
13115    }
13116    resolve_type_components_lexically_at_scoped(
13117        node,
13118        components,
13119        global,
13120        analyzer,
13121        visibility,
13122        ordinary_type_imports,
13123        file,
13124        source,
13125        direct_target,
13126        apply_structured_prefilter,
13127        preserve_alias,
13128        allow_compatible_foreign_import,
13129        lexical_scope,
13130    )
13131}
13132
13133#[allow(clippy::too_many_arguments)]
13134fn resolve_type_components_lexically_at_scoped(
13135    node: Node<'_>,
13136    components: &[String],
13137    global: bool,
13138    analyzer: &CppGraphSource<'_>,
13139    visibility: &VisibilityIndex<'_>,
13140    ordinary_type_imports: &OrdinaryTypeImportCell,
13141    file: &ProjectFile,
13142    source: &str,
13143    direct_target: Option<&CodeUnit>,
13144    apply_structured_prefilter: bool,
13145    preserve_alias: bool,
13146    allow_compatible_foreign_import: bool,
13147    mut lexical_scope: Vec<String>,
13148) -> LexicalTypeResolution {
13149    if !global
13150        && components.len() == 1
13151        // The two constant-time conditions run before the ancestor climb: each
13152        // `Node::parent` step re-descends from the root (#1927).
13153        && let Some(target) = direct_target
13154        && lexical_scope
13155            .last()
13156            .is_none_or(|last| last != &components[0])
13157        // A recovered class may contain a real member function nested inside
13158        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
13159        // XMLConstHandle). The nearest function_definition is then the member
13160        // itself, so inspect the complete ancestor chain.
13161        && has_malformed_wrapper_function_definition_ancestor(node)
13162        && let Some(indexed_namespace) =
13163            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
13164        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
13165    {
13166        lexical_scope = indexed_namespace;
13167    }
13168    resolve_type_components_in_authoritative_scope(
13169        node,
13170        components,
13171        global,
13172        analyzer,
13173        visibility,
13174        ordinary_type_imports,
13175        file,
13176        source,
13177        direct_target,
13178        apply_structured_prefilter,
13179        preserve_alias,
13180        allow_compatible_foreign_import,
13181        lexical_scope,
13182    )
13183}
13184
13185/// Resolve within a scope already proven by recovered syntax.
13186///
13187/// Unlike parser-derived scope, this scope must not be replaced with the
13188/// queried target's namespace: doing so would let target guidance override a
13189/// nearer declaration represented by the recovered syntax.
13190///
13191/// A qualifier that names a namespace alias in effect at `node` is rebound
13192/// here, once, for forward and inverse lookup alike: the rebound name is
13193/// looked up from the scope of the alias that supplies it. An ordinary name
13194/// the written spelling reaches at a deeper lexical tier shadows the alias,
13195/// and an alias whose target cannot be established resolves to nothing.
13196#[allow(clippy::too_many_arguments)]
13197pub(super) fn resolve_type_components_in_authoritative_scope(
13198    node: Node<'_>,
13199    components: &[String],
13200    global: bool,
13201    analyzer: &CppGraphSource<'_>,
13202    visibility: &VisibilityIndex<'_>,
13203    ordinary_type_imports: &OrdinaryTypeImportCell,
13204    file: &ProjectFile,
13205    source: &str,
13206    direct_target: Option<&CodeUnit>,
13207    apply_structured_prefilter: bool,
13208    preserve_alias: bool,
13209    allow_compatible_foreign_import: bool,
13210    lexical_scope: Vec<String>,
13211) -> LexicalTypeResolution {
13212    // Block scope is the innermost scope of unqualified lookup, so a local
13213    // class answers before every namespace, member and import tier below.
13214    if !global
13215        && let Some(resolution) =
13216            function_local_type_resolution(analyzer, file, node, components, source)
13217    {
13218        return resolution;
13219    }
13220    let resolve = |components: &[String], global: bool, lexical_scope: Vec<String>| {
13221        resolve_unaliased_type_components_in_authoritative_scope(
13222            node,
13223            components,
13224            global,
13225            analyzer,
13226            visibility,
13227            ordinary_type_imports,
13228            file,
13229            source,
13230            direct_target,
13231            apply_structured_prefilter,
13232            preserve_alias,
13233            allow_compatible_foreign_import,
13234            lexical_scope,
13235        )
13236    };
13237    let binding =
13238        visibility.bind_namespace_qualifier(file, node, components, global, &lexical_scope);
13239    let Some(depth) = binding.depth() else {
13240        return resolve(components, global, lexical_scope);
13241    };
13242    if let LexicalTypeResolution::Resolved {
13243        components: resolved,
13244        ..
13245    } = visibility.resolve_type_components_lexically(
13246        analyzer,
13247        file,
13248        components,
13249        global,
13250        &lexical_scope,
13251    ) && resolved.len().saturating_sub(components.len()) > depth
13252    {
13253        return resolve(components, global, lexical_scope);
13254    }
13255    match binding {
13256        NamespaceQualifierBinding::Bound {
13257            components,
13258            global,
13259            lexical_scope,
13260            ..
13261        } => resolve(&components, global, lexical_scope),
13262        NamespaceQualifierBinding::Unresolved { .. } => LexicalTypeResolution::Ambiguous,
13263        NamespaceQualifierBinding::Unaliased => unreachable!("an unaliased binding has no depth"),
13264    }
13265}
13266
13267/// Select conditional alias arms with the reference's guards before
13268/// canonicalizing them (#6902).
13269///
13270/// A resolved answer carries the arms as its candidates. An ambiguity carries
13271/// none, so the alias-preserving lookup at the same scope recovers them. A
13272/// proven selection resolves to its one type; undecided arms stay ambiguous
13273/// rather than naming either class.
13274#[allow(clippy::too_many_arguments)]
13275fn guarded_alias_type_resolution(
13276    node: Node<'_>,
13277    components: &[String],
13278    global: bool,
13279    lexical_scope: &[String],
13280    resolution: LexicalTypeResolution,
13281    analyzer: &CppGraphSource<'_>,
13282    visibility: &VisibilityIndex<'_>,
13283    file: &ProjectFile,
13284) -> LexicalTypeResolution {
13285    let preserved;
13286    let arms = match &resolution {
13287        LexicalTypeResolution::Resolved { .. } => &resolution,
13288        LexicalTypeResolution::Ambiguous => {
13289            preserved = visibility.resolve_type_components_lexically_for_forward(
13290                analyzer,
13291                file,
13292                components,
13293                global,
13294                lexical_scope,
13295            );
13296            &preserved
13297        }
13298        LexicalTypeResolution::Missing => return resolution,
13299    };
13300    let LexicalTypeResolution::Resolved {
13301        components: resolved_components,
13302        candidates,
13303        ..
13304    } = arms
13305    else {
13306        return resolution;
13307    };
13308    if !candidates
13309        .iter()
13310        .any(|candidate| declared_type_alias(analyzer, candidate))
13311    {
13312        return resolution;
13313    }
13314    match visibility.guarded_alias_selection_at_reference(analyzer, file, node, candidates) {
13315        None => resolution,
13316        Some(GuardedAliasSelection::Proven { unit, candidates }) => {
13317            LexicalTypeResolution::Resolved {
13318                unit,
13319                components: resolved_components.clone(),
13320                candidates,
13321            }
13322        }
13323        Some(GuardedAliasSelection::Alternatives(_)) => LexicalTypeResolution::Ambiguous,
13324    }
13325}
13326
13327#[allow(clippy::too_many_arguments)]
13328fn resolve_unaliased_type_components_in_authoritative_scope(
13329    node: Node<'_>,
13330    components: &[String],
13331    global: bool,
13332    analyzer: &CppGraphSource<'_>,
13333    visibility: &VisibilityIndex<'_>,
13334    ordinary_type_imports: &OrdinaryTypeImportCell,
13335    file: &ProjectFile,
13336    source: &str,
13337    direct_target: Option<&CodeUnit>,
13338    apply_structured_prefilter: bool,
13339    preserve_alias: bool,
13340    allow_compatible_foreign_import: bool,
13341    lexical_scope: Vec<String>,
13342) -> LexicalTypeResolution {
13343    if apply_structured_prefilter
13344        && direct_target.is_some()
13345        && !preserve_alias
13346        && !global
13347        && components.len() == 1
13348        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
13349    {
13350        return LexicalTypeResolution::Missing;
13351    }
13352    // A recovered macro-prefixed return type can share its global spelling
13353    // with aliases from mutually exclusive included headers. C++ lookup uses
13354    // the declaration physically present earlier in this file; the visibility
13355    // index deliberately retains every configuration alternative. Restore
13356    // that precedence only for the exact macro-decorated head (recovered by
13357    // the older grammar, or its clean macro-declaration reading) and lexical
13358    // tier.
13359    if !global
13360        && components.len() == 1
13361        && (recovered_macro_decorated_type_node(node).is_some()
13362            || macro_modifier_decorated_type_node(node))
13363        && let Some(resolution) = recovered_same_file_type_alias_resolution(
13364            node,
13365            components,
13366            analyzer,
13367            visibility,
13368            file,
13369            direct_target,
13370            &lexical_scope,
13371        )
13372    {
13373        return resolution;
13374    }
13375    if apply_structured_prefilter
13376        && let Some(target) = direct_target
13377        && !preserve_alias
13378        && !visibility.structured_type_reference_may_resolve_to_target(
13379            analyzer,
13380            file,
13381            components,
13382            global,
13383            &lexical_scope,
13384            target,
13385        )
13386    {
13387        return LexicalTypeResolution::Missing;
13388    }
13389    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13390    let lexical_normal_started = Instant::now();
13391    if report_stats {
13392        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
13393    }
13394    let normal = if preserve_alias {
13395        visibility.resolve_type_components_lexically_for_forward(
13396            analyzer,
13397            file,
13398            components,
13399            global,
13400            &lexical_scope,
13401        )
13402    } else {
13403        direct_target.map_or_else(
13404            || {
13405                visibility.resolve_type_components_lexically(
13406                    analyzer,
13407                    file,
13408                    components,
13409                    global,
13410                    &lexical_scope,
13411                )
13412            },
13413            |target| {
13414                visibility.resolve_type_components_lexically_for_target(
13415                    analyzer,
13416                    file,
13417                    components,
13418                    global,
13419                    &lexical_scope,
13420                    target,
13421                )
13422            },
13423        )
13424    };
13425    if report_stats {
13426        eprintln!(
13427            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
13428            lexical_normal_started.elapsed().as_millis(),
13429        );
13430    }
13431    // Conditional alias arms face this reference's guards before their targets
13432    // are canonicalized together (#6902). The preserving forward mode keeps the
13433    // alias itself, so it has nothing to select.
13434    let normal = if preserve_alias || cpp_template_reference_arguments(node, source).is_some() {
13435        normal
13436    } else {
13437        guarded_alias_type_resolution(
13438            node,
13439            components,
13440            global,
13441            &lexical_scope,
13442            normal,
13443            analyzer,
13444            visibility,
13445            file,
13446        )
13447    };
13448    let normal = match normal {
13449        LexicalTypeResolution::Resolved { ref unit, .. }
13450            if !visibility
13451                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
13452                && !direct_target.is_some_and(|target| {
13453                    let candidate_refs = visibility
13454                        .visible_identifier_candidates(file, target.identifier())
13455                        .collect::<Vec<_>>();
13456                    visibility.c_tag_declaration_family_matches_target(
13457                        analyzer,
13458                        file,
13459                        &candidate_refs,
13460                        target,
13461                    )
13462                }) =>
13463        {
13464            LexicalTypeResolution::Missing
13465        }
13466        resolution => resolution,
13467    };
13468    let normal_depth = match &normal {
13469        LexicalTypeResolution::Resolved { components, .. } => {
13470            Some(components.len().saturating_sub(1))
13471        }
13472        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
13473    };
13474    // Ordinary using-declarations participate in unqualified lookup at their
13475    // lexical scope. They therefore replace the resolver's terminal/global
13476    // fallback at the same or a shallower depth. A declaration in a more deeply
13477    // nested named scope is the closer lexical result and remains authoritative.
13478    // Ambiguous imports fail closed unless such a closer declaration exists.
13479    let ordinary_import_started = Instant::now();
13480    if report_stats {
13481        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
13482    }
13483    let ordinary_import_resolution = ordinary_type_import_resolution(
13484        node,
13485        components,
13486        global,
13487        analyzer,
13488        visibility,
13489        ordinary_type_imports,
13490        file,
13491        source,
13492        &lexical_scope,
13493        direct_target,
13494    );
13495    if report_stats {
13496        eprintln!(
13497            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
13498            ordinary_import_started.elapsed().as_millis(),
13499        );
13500    }
13501    let resolution = match ordinary_import_resolution {
13502        OrdinaryTypeImportResolution::Missing => normal,
13503        OrdinaryTypeImportResolution::Resolved {
13504            lexical_depth,
13505            is_direct,
13506            ..
13507        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
13508            || normal_depth.is_some_and(|depth| {
13509                depth > lexical_depth || (!is_direct && depth == lexical_depth)
13510            }) =>
13511        {
13512            normal
13513        }
13514        OrdinaryTypeImportResolution::Resolved {
13515            target,
13516            target_components,
13517            ..
13518        } => visibility.resolve_imported_type_candidate(
13519            analyzer,
13520            file,
13521            &target,
13522            &target_components,
13523            direct_target,
13524            preserve_alias,
13525        ),
13526        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
13527            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
13528        {
13529            normal
13530        }
13531        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13532    };
13533    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
13534        return resolution;
13535    }
13536
13537    // A foreign header can contribute an exact using binding from a build
13538    // configuration that is compatible with, but not implied by, the current
13539    // reference. Forward navigation has no "unproven" result channel, so it
13540    // may still navigate through one unique structured import after ordinary
13541    // lookup misses. Inverse attribution keeps using its target-guided path
13542    // and records the same evidence as unproven (#940).
13543    match compatible_foreign_type_import_resolution(
13544        node,
13545        components,
13546        global,
13547        analyzer,
13548        visibility,
13549        ordinary_type_imports,
13550        file,
13551        source,
13552        &lexical_scope,
13553        None,
13554    ) {
13555        OrdinaryTypeImportResolution::Missing => resolution,
13556        OrdinaryTypeImportResolution::Resolved {
13557            target,
13558            target_components,
13559            ..
13560        } => visibility.resolve_imported_type_candidate(
13561            analyzer,
13562            file,
13563            &target,
13564            &target_components,
13565            None,
13566            true,
13567        ),
13568        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13569    }
13570}
13571
13572fn recovered_same_file_type_alias_resolution(
13573    node: Node<'_>,
13574    components: &[String],
13575    analyzer: &CppGraphSource<'_>,
13576    visibility: &VisibilityIndex<'_>,
13577    file: &ProjectFile,
13578    direct_target: Option<&CodeUnit>,
13579    lexical_scope: &[String],
13580) -> Option<LexicalTypeResolution> {
13581    debug_assert_eq!(components.len(), 1);
13582    debug_assert!(
13583        recovered_macro_decorated_type_node(node).is_some()
13584            || macro_modifier_decorated_type_node(node)
13585    );
13586    let alias_provider = analyzer.type_alias_provider()?;
13587    for qualified in lexical_component_tiers(components, false, lexical_scope) {
13588        let candidates = visibility
13589            .visible_identifier_candidates(file, &components[0])
13590            .filter(|candidate| {
13591                candidate.source() == file
13592                    && canonical_cpp_scope_components(candidate) == qualified
13593                    && visibility
13594                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
13595            })
13596            .collect::<Vec<_>>();
13597        if candidates.is_empty() {
13598            continue;
13599        }
13600        if candidates
13601            .iter()
13602            .any(|candidate| !alias_provider.is_type_alias(candidate))
13603        {
13604            return None;
13605        }
13606        let unit = if let Some(target) = direct_target {
13607            visibility.unique_type_candidate_preserving_target(
13608                analyzer,
13609                file,
13610                &candidates,
13611                target,
13612            )?
13613        } else {
13614            let first = candidates[0];
13615            if candidates
13616                .iter()
13617                .any(|candidate| !same_visible_symbol(candidate, first))
13618            {
13619                return None;
13620            }
13621            first.clone()
13622        };
13623        return Some(LexicalTypeResolution::Resolved {
13624            unit,
13625            components: qualified,
13626            candidates: candidates.into_iter().cloned().collect(),
13627        });
13628    }
13629    None
13630}
13631
13632fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13633    matches!(
13634        structured_owner_context_resolution(node, ctx),
13635        StructuredOwnerContextResolution::SelfTarget
13636            | StructuredOwnerContextResolution::InheritedTarget
13637    )
13638}
13639
13640/// A bare/`this->` member call whose name resolves, through the enclosing class's base
13641/// hierarchy or through a lexically enclosing class, to the target member declared on
13642/// another owner. This is a genuine external usage of that owner's member rather than a
13643/// same-type self call.
13644fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13645    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
13646        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
13647    }) else {
13648        return matches!(
13649            structured_owner_context_resolution(node, ctx),
13650            StructuredOwnerContextResolution::InheritedTarget
13651        );
13652    };
13653    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13654        return false;
13655    };
13656    let chain = structured_enclosing_owner_chain(node, ctx);
13657    let Some(innermost) = chain.first() else {
13658        return false;
13659    };
13660    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13661        return false;
13662    }
13663    let Some(arity) = ctx
13664        .visibility
13665        .call_arity_evidence(ctx.file, call, ctx.source)
13666        .exact()
13667    else {
13668        return false;
13669    };
13670    for enclosing_owner in &chain {
13671        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13672            return true;
13673        }
13674        match resolve_declaring_callable_owner(
13675            &ctx.analyzer,
13676            ctx.visibility,
13677            ctx.file,
13678            cached_declaring_member_owner(enclosing_owner, ctx),
13679            &ctx.spec.member_name,
13680            arity,
13681        ) {
13682            EnclosingMemberOwnerResolution::Owner(owner) => {
13683                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13684            }
13685            EnclosingMemberOwnerResolution::Ambiguous => return false,
13686            // The name is not declared in this class or its bases, so C++
13687            // unqualified lookup continues in the next enclosing class (#3095).
13688            EnclosingMemberOwnerResolution::Missing => {}
13689        }
13690    }
13691    false
13692}
13693
13694fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13695    matches!(
13696        structured_owner_context_resolution(node, ctx),
13697        StructuredOwnerContextResolution::NonTarget
13698    )
13699}
13700
13701fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13702    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13703        return false;
13704    };
13705    let mut current = ctx.ancestry.parent(node);
13706    while let Some(parent) = current {
13707        if parent.kind() == "function_definition" {
13708            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
13709                return false;
13710            };
13711            if let Some(owners) = out_of_line_member_definition_owner(
13712                &ctx.analyzer,
13713                ctx.visibility,
13714                ctx.file,
13715                ctx.source,
13716                owner_lookup,
13717            ) && let Some((_, owner)) = owners.innermost()
13718            {
13719                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
13720            }
13721            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
13722                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13723            }
13724            return false;
13725        }
13726        current = ctx.ancestry.parent(parent);
13727    }
13728    false
13729}
13730
13731#[derive(Clone, Copy)]
13732enum StructuredOwnerContextResolution {
13733    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
13734    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
13735    SelfTarget,
13736    /// The enclosing class does not declare the member; lookup reaches the target owner
13737    /// through a base of that class, or through a lexically enclosing class (a nested
13738    /// class calling the outer class's member). Either way the reference is a genuine
13739    /// external usage OF that owner's member (e.g. `Derived` calling inherited
13740    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
13741    InheritedTarget,
13742    NonTarget,
13743    Ambiguous,
13744    Missing,
13745}
13746
13747fn structured_owner_context_resolution(
13748    node: Node<'_>,
13749    ctx: &ScanCtx<'_>,
13750) -> StructuredOwnerContextResolution {
13751    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13752        return StructuredOwnerContextResolution::Missing;
13753    };
13754    let chain = structured_enclosing_owner_chain(node, ctx);
13755    let Some(innermost) = chain.first() else {
13756        return StructuredOwnerContextResolution::Missing;
13757    };
13758    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13759        return StructuredOwnerContextResolution::SelfTarget;
13760    }
13761    // Unqualified lookup searches the enclosing class and its bases, then each lexically
13762    // enclosing class outward, and stops at the first scope that declares the name (#3095).
13763    // Only the immediately enclosing class makes a reference a same-type self call; any
13764    // other owner it reaches is a genuine usage of that owner's member.
13765    for enclosing_owner in &chain {
13766        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13767            return StructuredOwnerContextResolution::InheritedTarget;
13768        }
13769        match cached_declaring_member_owner(enclosing_owner, ctx) {
13770            EnclosingMemberOwnerResolution::Owner(owner)
13771                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
13772            {
13773                return StructuredOwnerContextResolution::InheritedTarget;
13774            }
13775            EnclosingMemberOwnerResolution::Owner(_) => {
13776                return StructuredOwnerContextResolution::NonTarget;
13777            }
13778            EnclosingMemberOwnerResolution::Ambiguous => {
13779                return StructuredOwnerContextResolution::Ambiguous;
13780            }
13781            EnclosingMemberOwnerResolution::Missing => {}
13782        }
13783    }
13784    StructuredOwnerContextResolution::Missing
13785}
13786
13787fn cached_declaring_member_owner(
13788    receiver_owner: &CodeUnit,
13789    ctx: &ScanCtx<'_>,
13790) -> EnclosingMemberOwnerResolution {
13791    if let Some(cached) = ctx
13792        .member_owner_cache
13793        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
13794            cells.borrow().get(receiver_owner).cloned()
13795        })
13796    {
13797        return cached;
13798    }
13799    let resolved = resolve_declaring_member_owner(
13800        &ctx.analyzer,
13801        ctx.visibility,
13802        ctx.file,
13803        receiver_owner,
13804        &ctx.spec.member_name,
13805    );
13806    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13807        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13808    } else {
13809        resolved
13810    };
13811    ctx.member_owner_cache
13812        .read(&ctx.visibility.reads.memo_attribution(), |cells| {
13813            cells
13814                .borrow_mut()
13815                .insert(receiver_owner.clone(), resolved.clone())
13816        });
13817    resolved
13818}
13819
13820/// Recover a member's declaring owner when parser recovery omitted its
13821/// in-class declaration but retained an out-of-line definition. Ordinary
13822/// visible-member lookup runs first. The structured definition index then
13823/// supplies the missing member fact at each hierarchy level, so an indexed
13824/// derived override still hides the queried base member and distinct base
13825/// paths still fail closed.
13826fn indexed_declaring_owner_for_recovered_member(
13827    receiver_owner: &CodeUnit,
13828    ctx: &ScanCtx<'_>,
13829) -> EnclosingMemberOwnerResolution {
13830    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13831        return EnclosingMemberOwnerResolution::Missing;
13832    };
13833    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13834        return EnclosingMemberOwnerResolution::Missing;
13835    }
13836    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13837        return EnclosingMemberOwnerResolution::Missing;
13838    };
13839    let Some(receiver_owner) =
13840        ctx.visibility
13841            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13842    else {
13843        return EnclosingMemberOwnerResolution::Ambiguous;
13844    };
13845    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13846        ctx.visibility
13847            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13848    }) else {
13849        return EnclosingMemberOwnerResolution::Missing;
13850    };
13851
13852    let owner_declares_member = |owner: &CodeUnit| {
13853        if same_visible_symbol(owner, &target_owner) {
13854            return true;
13855        }
13856        let mut member_fq = owner.fq().clone();
13857        member_fq.push(
13858            ctx.spec
13859                .target
13860                .fq()
13861                .last()
13862                .expect("a method target has a terminal member segment"),
13863        );
13864        ctx.analyzer
13865            .definitions(&member_fq.display(segment_interner()))
13866            .any(|child| child.is_function())
13867    };
13868    if owner_declares_member(&receiver_owner) {
13869        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13870    }
13871
13872    let mut stack = hierarchy
13873        .get_direct_ancestors(&receiver_owner)
13874        .definite()
13875        .to_vec();
13876    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13877    let mut declaring_owner = None;
13878    while let Some(raw_owner) = stack.pop() {
13879        let Some(owner) =
13880            ctx.visibility
13881                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13882        else {
13883            return EnclosingMemberOwnerResolution::Ambiguous;
13884        };
13885        let propagated = propagated_counts.entry(owner.clone()).or_default();
13886        if *propagated == 2 {
13887            continue;
13888        }
13889        *propagated += 1;
13890        if owner_declares_member(&owner) {
13891            if declaring_owner.is_some() {
13892                return EnclosingMemberOwnerResolution::Ambiguous;
13893            }
13894            declaring_owner = Some(owner);
13895            continue;
13896        }
13897        stack.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
13898    }
13899    declaring_owner
13900        .map(EnclosingMemberOwnerResolution::Owner)
13901        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13902}
13903
13904/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13905/// lookup searches each in turn before it reaches the enclosing namespaces.
13906fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13907    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13908        return Vec::new();
13909    };
13910    // The innermost owner is whatever enclosing-owner resolution produced, so
13911    // it is kept as given; only the walk outward is filtered to classes.
13912    brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13913        ctx.analyzer.parent_of(unit)
13914    })
13915    .collect()
13916}
13917
13918fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13919    // Declaration recovery can index the true class/member ranges even when
13920    // the original error tree wraps that region in a bogus function. Prefer
13921    // the analyzer's exact enclosing-owner graph at the reference byte before
13922    // interpreting such a wrapper as a real callable owner.
13923    if (has_recovered_class_shape_ancestor(node)
13924        || has_malformed_wrapper_function_definition_ancestor(node))
13925        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13926    {
13927        return Some(owner);
13928    }
13929    let mut current = ctx.ancestry.parent(node);
13930    while let Some(parent) = current {
13931        if parent.kind() == "function_definition" {
13932            let owner_lookup = function_definition_owner_lookup_node(parent);
13933            if let Some(owner_lookup) = owner_lookup
13934                && let Some(owners) = out_of_line_member_definition_owner(
13935                    &ctx.analyzer,
13936                    ctx.visibility,
13937                    ctx.file,
13938                    ctx.source,
13939                    owner_lookup,
13940                )
13941                && let Some((_, owner)) = owners.innermost()
13942            {
13943                return Some(owner.clone());
13944            }
13945            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13946                return Some(owner);
13947            }
13948            if let Some(owner) = enclosing_context(parent, ctx)
13949                .owner
13950                .filter(|owner| owner.is_class())
13951            {
13952                return Some(owner);
13953            }
13954            if let Some(owner_lookup) = owner_lookup
13955                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13956            {
13957                return Some(owner);
13958            }
13959            break;
13960        }
13961        current = ctx.ancestry.parent(parent);
13962    }
13963    enclosing_context(node, ctx)
13964        .owner
13965        .filter(|owner| owner.is_class())
13966}
13967
13968fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13969    let target_owner = ctx.spec.owner.as_ref()?;
13970    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13971    let owner_name = owner_components.last()?;
13972    let mut candidates = Vec::new();
13973    for candidate in ctx
13974        .visibility
13975        .visible_identifier_candidates(ctx.file, owner_name)
13976        .filter(|candidate| candidate.is_class())
13977    {
13978        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13979            brokk_bifrost_core::analyzer::Language::Cpp,
13980            &cpp_name_for(candidate),
13981        );
13982        if !components.ends_with(&owner_components)
13983            || candidates
13984                .iter()
13985                .any(|existing| same_logical_symbol(existing, candidate))
13986        {
13987            continue;
13988        }
13989        candidates.push(candidate.clone());
13990    }
13991    let [candidate] = candidates.as_slice() else {
13992        return None;
13993    };
13994    (same_logical_symbol(candidate, target_owner)
13995        && target_group_contains_owner_peer(candidate, ctx))
13996    .then(|| candidate.clone())
13997}