Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

1use crate::call_match::{
2    CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_forwarding_call_argument,
3    cpp_literal_arg_type, cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6    CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7    cpp_sentinel_recovered_scope_for_node, enclosing_cpp_namespace_nodes,
8    is_recovered_exported_class_base_type_node, node_text, normalize_cpp_whitespace,
9    recovered_macro_return_type_node,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
13use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
14use crate::graph::hits::{
15    enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
16    push_declared_reference_hit, push_definition_hit, push_hit, push_recovered_definition_hit,
17    push_recursive_reference_hit, push_reference_hit_range, push_self_receiver_hit, push_type_hit,
18    push_type_hit_range, push_unproven_definition_hit, push_unproven_hit,
19    push_unproven_reference_hit_range,
20};
21use crate::graph::namespace_alias::NamespaceQualifierBinding;
22use crate::graph::resolver::*;
23use crate::graph::syntax::{
24    function_macro_replacement_span, object_macro_replacement_type_references,
25    qualified_callable_value, receiver_is_self_like,
26};
27use crate::graph_support::CppSource;
28use brokk_bifrost_core::analyzer::fq_name::segment_interner;
29use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
30use brokk_bifrost_core::analyzer::query_token::QueryToken;
31use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_is_within;
32use brokk_bifrost_core::analyzer::tree_walk::{
33    ParentIndex, WalkControl, children_iter, first_named_child_of_kind, parent_if_field,
34    push_named_children_reversed, root_node, walk_named_tree_preorder,
35};
36use brokk_bifrost_core::analyzer::usages::common::same_node;
37use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
38use brokk_bifrost_core::analyzer::usages::local_inference::{
39    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
40};
41use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
42use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
43use brokk_bifrost_core::hash::{HashMap, HashSet};
44#[cfg(any(test, feature = "test-support"))]
45use std::cell::Cell;
46use std::cell::RefCell;
47use std::collections::BTreeSet;
48use std::sync::Arc;
49use std::time::Instant;
50use tree_sitter::Node;
51
52#[cfg(any(test, feature = "test-support"))]
53thread_local! {
54    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
55    static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
56}
57
58#[cfg(any(test, feature = "test-support"))]
59pub fn reset_type_reference_candidate_scan_count_for_test() {
60    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
61}
62
63#[cfg(any(test, feature = "test-support"))]
64pub fn type_reference_candidate_scan_count_for_test() -> usize {
65    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
66}
67
68pub struct ScanState<'a> {
69    pub max_usages: usize,
70    pub hits: &'a mut BTreeSet<UsageHit>,
71    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
72    pub raw_match_count: &'a mut usize,
73    pub limit_exceeded: &'a mut bool,
74}
75
76pub struct ScanCtx<'a> {
77    pub analyzer: CppGraphSource<'a>,
78    pub visibility: &'a VisibilityIndex<'a>,
79    pub file: &'a ProjectFile,
80    pub source: &'a str,
81    /// The parent of every node in the file being scanned, recorded by one
82    /// downward pass when the scan starts. `Node::parent` recovers a parent by
83    /// re-descending from the root, so an ancestor climb is quadratic in depth
84    /// and the scan climbs from more than a dozen call sites per visited node
85    /// (#1927, #3097).
86    pub ancestry: ParentIndex<'a>,
87    ordinary_type_imports: OrdinaryTypeImportCell,
88    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
89    class_ranges: Option<&'a ClassRangeIndex>,
90    pub line_starts: &'a [usize],
91    pub spec: &'a TargetSpec,
92    pub target_group: &'a HashSet<CodeUnit>,
93    pub has_proven_visible_type_target: bool,
94    uses_c_semantics: bool,
95    type_reference_component_names: HashSet<String>,
96    pub target_declaration_ranges: Vec<Range>,
97    target_macro_declaration_bytes: Vec<usize>,
98    pub bindings: LocalInferenceEngine<CppScanBinding>,
99    local_shadows: LocalInferenceEngine<()>,
100    using_enum_owners: ScopedUsingEnumOwners,
101    semantic_using_enum_owners: SemanticUsingEnumOwners,
102    needs_using_enum_member_resolution: bool,
103    pub hits: &'a mut BTreeSet<UsageHit>,
104    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
105    pub raw_match_count: &'a mut usize,
106    pub max_usages: usize,
107    pub external_hit_count: usize,
108    pub limit_exceeded: &'a mut bool,
109    pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
110    pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
111    lexical_scope_cache: LexicalScopeCache,
112    lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
113    member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
114    global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
115    receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
116}
117
118impl ScanCtx<'_> {
119    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
120        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
121    }
122}
123
124#[derive(Clone, Default)]
125pub struct EnclosingContext {
126    pub enclosing: Option<CodeUnit>,
127    pub owner: Option<CodeUnit>,
128}
129
130pub fn prepare_file(
131    cpp: &dyn CppSource,
132    token: QueryToken<'_>,
133    file: &ProjectFile,
134) -> Option<Arc<PreparedSyntaxTree>> {
135    cpp.prepared_syntax(token, file)
136}
137
138#[allow(clippy::too_many_arguments)]
139pub fn scan_prepared_file(
140    analyzer: &CppGraphSource<'_>,
141    visibility: &VisibilityIndex<'_>,
142    file: &ProjectFile,
143    prepared: &PreparedSyntaxTree,
144    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
145    class_ranges: Option<&ClassRangeIndex>,
146    spec: &TargetSpec,
147    target_group: &HashSet<CodeUnit>,
148    state: &mut ScanState<'_>,
149) {
150    if *state.limit_exceeded {
151        return;
152    }
153    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
154    let has_proven_visible_type_target = spec.kind == TargetKind::Type
155        && (target_group.iter().any(|target| {
156            same_logical_symbol(target, &spec.target)
157                && visibility.is_physically_visible(file, target)
158        }) || {
159            let candidates = visibility
160                .visible_identifier_candidates(file, spec.target.identifier())
161                .collect::<Vec<_>>();
162            visibility.c_tag_declaration_family_matches_target(
163                analyzer,
164                file,
165                &candidates,
166                &spec.target,
167            )
168        });
169    if spec.kind == TargetKind::Type
170        && !has_proven_visible_type_target
171        && visibility
172            .visible_identifier_candidates(file, spec.target.identifier())
173            .any(|candidate| {
174                candidate != &spec.target
175                    && !target_group.contains(candidate)
176                    && same_logical_symbol(candidate, &spec.target)
177                    && visibility.is_physically_visible(file, candidate)
178                    && !visibility.c_tag_declaration_family_matches_target(
179                        analyzer,
180                        file,
181                        std::slice::from_ref(&candidate),
182                        &spec.target,
183                    )
184            })
185    {
186        return;
187    }
188    let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
189        analyzer.ranges(&spec.target)
190    } else {
191        Vec::new()
192    };
193    let target_declaration_ranges = if spec.kind == TargetKind::Type {
194        target_group
195            .iter()
196            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
197            .flat_map(|target| analyzer.ranges(target))
198            .collect()
199    } else if spec.target.source() == file {
200        if spec.kind == TargetKind::Macro {
201            macro_target_declaration_ranges.clone()
202        } else {
203            analyzer.ranges(&spec.target)
204        }
205    } else {
206        Vec::new()
207    };
208    let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
209        visibility.macro_declaration_bytes(&spec.target, &macro_target_declaration_ranges)
210    } else {
211        Vec::new()
212    };
213    let type_reference_component_names = if spec.kind == TargetKind::Type {
214        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
215    } else {
216        HashSet::default()
217    };
218    if spec.kind == TargetKind::Type
219        && !file_may_reference_type_target(
220            prepared.tree().root_node(),
221            prepared.source(),
222            analyzer,
223            visibility,
224            file,
225            &spec.target,
226            &type_reference_component_names,
227        )
228    {
229        return;
230    }
231    let ordinary_type_imports = initialized_ordinary_type_imports(
232        prepared.tree().root_node(),
233        analyzer,
234        visibility,
235        file,
236        prepared.source(),
237    );
238    let external_hit_count = state
239        .hits
240        .iter()
241        .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
242        .count();
243    let mut ctx = ScanCtx {
244        analyzer: *analyzer,
245        visibility,
246        file,
247        source: prepared.source(),
248        ancestry: ParentIndex::new(prepared.tree().root_node()),
249        ordinary_type_imports,
250        recovered_sentinel_classes,
251        class_ranges,
252        line_starts: prepared.line_starts(),
253        spec,
254        target_group,
255        has_proven_visible_type_target,
256        uses_c_semantics: analyzer.reference_uses_c_semantics(file),
257        type_reference_component_names,
258        target_declaration_ranges,
259        target_macro_declaration_bytes,
260        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
261        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
262        using_enum_owners: ScopedUsingEnumOwners::new(),
263        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
264        needs_using_enum_member_resolution,
265        hits: state.hits,
266        unproven_hits: state.unproven_hits,
267        raw_match_count: state.raw_match_count,
268        max_usages: state.max_usages,
269        external_hit_count,
270        limit_exceeded: state.limit_exceeded,
271        enclosing_cache: RefCell::new(HashMap::default()),
272        enclosing_owner_cache: RefCell::new(HashMap::default()),
273        lexical_scope_cache: LexicalScopeCache::new(visibility, file),
274        lexical_free_function_cache: RefCell::new(HashMap::default()),
275        member_owner_cache: RefCell::new(HashMap::default()),
276        global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
277        receiver_canonical_type_cache: RefCell::new(HashMap::default()),
278    };
279    if needs_using_enum_member_resolution {
280        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
281    }
282    if spec.kind == TargetKind::Macro {
283        scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
284        return;
285    }
286    scan_node(prepared.tree().root_node(), &mut ctx);
287}
288
289/// Scan a macro target without maintaining the declaration and lexical state
290/// required by ordinary C and C++ symbols. Macro activation comes entirely
291/// from [`VisibilityIndex`], so rebuilding unrelated binding state at every
292/// node makes generated tables needlessly expensive.
293fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
294    walk_named_tree_preorder(root, true, |node| {
295        if *ctx.limit_exceeded {
296            return WalkControl::Break;
297        }
298        maybe_record_macro_hit(node, ctx);
299        WalkControl::Continue
300    });
301}
302
303enum UsingEnumDeclarationScope {
304    Block,
305    Class(CodeUnit),
306    Namespace(Vec<String>),
307    UnsupportedClass,
308}
309
310fn resolved_using_enum_declaration(
311    node: Node<'_>,
312    ctx: &ScanCtx<'_>,
313) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
314    let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
315        node,
316        &ctx.analyzer,
317        ctx.visibility,
318        &ctx.ordinary_type_imports,
319        ctx.file,
320        ctx.source,
321    ) else {
322        return None;
323    };
324    Some((unit, using_enum_declaration_scope(node, ctx)))
325}
326
327fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
328    let mut current = ctx.ancestry.parent(node);
329    while let Some(parent) = current {
330        if matches!(
331            parent.kind(),
332            "compound_statement"
333                | "function_definition"
334                | "lambda_expression"
335                | "for_statement"
336                | "while_statement"
337                | "if_statement"
338        ) {
339            return UsingEnumDeclarationScope::Block;
340        }
341        if matches!(
342            parent.kind(),
343            "class_specifier" | "struct_specifier" | "union_specifier"
344        ) {
345            let resolution = enclosing_lexical_scope_components(
346                node,
347                &ctx.analyzer,
348                ctx.visibility,
349                ctx.file,
350                ctx.source,
351            );
352            if let LexicalScopeResolution::Resolved(components) = resolution
353                && let LexicalTypeResolution::Resolved { unit, .. } =
354                    ctx.visibility.resolve_type_components_lexically(
355                        &ctx.analyzer,
356                        ctx.file,
357                        &components,
358                        true,
359                        &[],
360                    )
361            {
362                return UsingEnumDeclarationScope::Class(unit);
363            }
364            return UsingEnumDeclarationScope::UnsupportedClass;
365        }
366        current = ctx.ancestry.parent(parent);
367    }
368    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
369}
370
371fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
372    let mut stack = vec![root];
373    while let Some(node) = stack.pop() {
374        if node.kind() == "using_declaration"
375            && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
376        {
377            match scope {
378                UsingEnumDeclarationScope::Block => {}
379                UsingEnumDeclarationScope::Class(class) => {
380                    ctx.semantic_using_enum_owners.import_class(class, unit);
381                }
382                UsingEnumDeclarationScope::Namespace(namespace) => {
383                    ctx.semantic_using_enum_owners.import_namespace(
384                        namespace,
385                        node.start_byte(),
386                        unit,
387                    );
388                }
389                UsingEnumDeclarationScope::UnsupportedClass => {}
390            }
391        }
392        push_named_children_reversed(node, &mut stack);
393    }
394}
395
396fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
397    struct Frame<'tree> {
398        node: Node<'tree>,
399        next_child: Option<Node<'tree>>,
400        previous_child: Option<Node<'tree>>,
401        entered: bool,
402        enters_scope: bool,
403        enters_using_enum_scope: bool,
404        fractured_function_scope: bool,
405    }
406    impl<'tree> Frame<'tree> {
407        fn new(node: Node<'tree>) -> Self {
408            Self {
409                node,
410                next_child: node.named_child(0),
411                previous_child: None,
412                entered: false,
413                enters_scope: false,
414                enters_using_enum_scope: false,
415                fractured_function_scope: false,
416            }
417        }
418    }
419    if *ctx.limit_exceeded {
420        return;
421    }
422    let mut stack = vec![Frame::new(node)];
423    while let Some(current) = stack.last_mut() {
424        let node = current.node;
425        let translation_unit = node.kind() == "translation_unit";
426        if !current.entered {
427            current.entered = true;
428            current.enters_scope = matches!(
429                node.kind(),
430                "compound_statement"
431                    | "function_definition"
432                    | "lambda_expression"
433                    | "for_statement"
434                    | "for_range_loop"
435                    | "while_statement"
436                    | "if_statement"
437            );
438            current.enters_using_enum_scope = ctx.needs_using_enum_member_resolution
439                && (current.enters_scope
440                    || matches!(
441                        node.kind(),
442                        "namespace_definition"
443                            | "class_specifier"
444                            | "struct_specifier"
445                            | "union_specifier"
446                    ));
447            if current.enters_scope {
448                ctx.bindings.enter_scope();
449                ctx.local_shadows.enter_scope();
450            }
451            if current.enters_using_enum_scope {
452                ctx.using_enum_owners.enter_scope();
453            }
454            seed_declarations(node, ctx);
455            maybe_record_hit(node, ctx);
456        }
457        if let Some(child) = current.previous_child.take()
458            && !*ctx.limit_exceeded
459            && translation_unit
460            && macro_fractured_function(child)
461            && macro_fracture_boundary(child).is_some()
462        {
463            if current.fractured_function_scope {
464                ctx.bindings.exit_scope();
465                ctx.local_shadows.exit_scope();
466            }
467            ctx.bindings.enter_scope();
468            ctx.local_shadows.enter_scope();
469            seed_fractured_function_prefix(child, ctx);
470            current.fractured_function_scope = true;
471        }
472        // Unwind every entered scope once the hit limit has been reached.
473        if !*ctx.limit_exceeded
474            && let Some(child) = current.next_child.take()
475        {
476            current.next_child = child.next_named_sibling();
477            if translation_unit && current.fractured_function_scope && child.kind() == "ERROR" {
478                ctx.bindings.exit_scope();
479                ctx.local_shadows.exit_scope();
480                current.fractured_function_scope = false;
481            }
482            current.previous_child = Some(child);
483            stack.push(Frame::new(child));
484            continue;
485        }
486        if current.fractured_function_scope {
487            ctx.bindings.exit_scope();
488            ctx.local_shadows.exit_scope();
489        }
490        if current.enters_scope {
491            ctx.bindings.exit_scope();
492            ctx.local_shadows.exit_scope();
493        }
494        if current.enters_using_enum_scope {
495            ctx.using_enum_owners.exit_scope();
496        }
497        stack.pop();
498    }
499}
500
501fn macro_fractured_function(node: Node<'_>) -> bool {
502    if node.kind() != "function_definition" {
503        return false;
504    }
505    let Some(body) = node.child_by_field_name("body") else {
506        return false;
507    };
508    let mut stack = vec![body];
509    while let Some(current) = stack.pop() {
510        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
511            return true;
512        }
513        let mut cursor = current.walk();
514        stack.extend(current.named_children(&mut cursor));
515    }
516    false
517}
518
519/// Return the recovery boundary following a fractured macro-generated callable.
520pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
521    let first_orphan = node.next_named_sibling()?;
522    if !matches!(
523        first_orphan.kind(),
524        "expression_statement"
525            | "compound_statement"
526            | "preproc_if"
527            | "preproc_ifdef"
528            | "if_statement"
529            | "for_statement"
530            | "while_statement"
531            | "do_statement"
532            | "return_statement"
533    ) {
534        return None;
535    }
536    let mut sibling = Some(first_orphan);
537    while let Some(current) = sibling {
538        if current.kind() == "ERROR" {
539            return Some(current.end_byte());
540        }
541        if matches!(
542            current.kind(),
543            "function_definition" | "declaration" | "type_definition"
544        ) {
545            return None;
546        }
547        sibling = current.next_named_sibling();
548    }
549    None
550}
551
552fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
553    let cutoff = node.end_byte().saturating_sub(1);
554    seed_fractured_active_path(node, cutoff, ctx);
555}
556
557fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
558    let mut stack = vec![node];
559    while let Some(node) = stack.pop() {
560        if node.start_byte() >= cutoff {
561            continue;
562        }
563        let enters_scope = matches!(
564            node.kind(),
565            "compound_statement"
566                | "function_definition"
567                | "lambda_expression"
568                | "for_range_loop"
569                | "for_statement"
570                | "while_statement"
571                | "if_statement"
572                | "class_specifier"
573                | "struct_specifier"
574                | "union_specifier"
575        );
576        if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
577            continue;
578        }
579        seed_declarations(node, ctx);
580        push_named_children_reversed(node, &mut stack);
581    }
582}
583
584fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
585    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
586        || (ctx.spec.kind != TargetKind::Type
587            && indexed_recovered_class_field_declaration(node, ctx))
588    {
589        return;
590    }
591    match node.kind() {
592        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
593        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
594        "for_range_loop" => seed_range_binding(node, ctx),
595        "expression_statement" | "macro_type_specifier" => {
596            seed_function_macro_local_binding(node, ctx)
597        }
598        "assignment_expression" => seed_function_macro_container_binding(node, ctx),
599        "using_declaration" => seed_using_enum(node, ctx),
600        _ => {}
601    }
602}
603
604fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
605    let Some(binding) = ctx
606        .visibility
607        .function_macro_local_binding(ctx.file, node, ctx.source)
608    else {
609        return;
610    };
611    if ctx.spec.kind == TargetKind::Type {
612        ctx.bindings.declare_shadow(binding.name);
613        return;
614    }
615    let normalized = normalize_cpp_type_name(&binding.type_name);
616    let unit = binding
617        .type_node
618        .and_then(|type_node| {
619            ctx.visibility
620                .resolve_type_node_result(ctx.file, type_node, ctx.source)
621                .ok()
622                .flatten()
623        })
624        .or_else(|| {
625            ctx.visibility
626                .canonical_type_for_reference(ctx.file, &normalized)
627        })
628        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
629    ctx.bindings.seed_symbol(
630        binding.name,
631        CppScanBinding::from_type_name(
632            normalized,
633            unit,
634            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
635        ),
636    );
637}
638
639fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
640    let Some(binding) =
641        ctx.visibility
642            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
643    else {
644        return;
645    };
646    let normalized = normalize_cpp_type_name(&binding.type_name);
647    let unit = binding.proven_unit.clone().or_else(|| {
648        binding
649            .type_node
650            .and_then(|type_node| {
651                ctx.visibility
652                    .resolve_type_node_result(ctx.file, type_node, ctx.source)
653                    .ok()
654                    .flatten()
655            })
656            .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
657    });
658    if let Some(unit) = unit {
659        ctx.bindings.seed_symbol(
660            binding.name,
661            CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
662        );
663    }
664}
665
666fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
667    if node.kind() != "declaration"
668        || !has_function_scope_ancestor(node)
669        || has_ancestor_kind(node, "lambda_expression")
670        || !(has_recovered_class_shape_ancestor(node)
671            || has_malformed_wrapper_function_definition_ancestor(node))
672    {
673        return false;
674    }
675    let context = enclosing_context(node, ctx);
676    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
677        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
678}
679
680fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
681    if !ctx.needs_using_enum_member_resolution {
682        return;
683    }
684    if let Some((unit, UsingEnumDeclarationScope::Block)) =
685        resolved_using_enum_declaration(node, ctx)
686    {
687        ctx.using_enum_owners.import(unit);
688    }
689}
690
691fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
692    // Class and local-struct fields are resolved from their enclosing owner
693    // when they appear as an unqualified receiver. Seeding them into the
694    // function-wide binding scope would make same-spelled fields from sibling
695    // owners overwrite one another before the owner-aware path runs.
696    if node.kind() == "field_declaration" {
697        return;
698    }
699    let type_node = node
700        .child_by_field_name("type")
701        .or_else(|| first_type_child(node));
702    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
703    let mut cursor = node.walk();
704    for child in node.named_children(&mut cursor) {
705        let declarator = declaration_declarator(node, child);
706        let Some(declarator) = declarator else {
707            continue;
708        };
709        let Some(name) = extract_variable_name(declarator, ctx.source) else {
710            continue;
711        };
712        if declarator.kind() == "function_declarator"
713            && !constructor_style_local_declaration(
714                ctx.visibility,
715                ctx.file,
716                ctx.source,
717                declarator,
718                type_text.as_deref(),
719                &ctx.bindings,
720            )
721        {
722            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
723                ctx.local_shadows.declare_shadow(name);
724            }
725            continue;
726        }
727        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
728            ctx.local_shadows.declare_shadow(name.clone());
729        }
730        if ctx.spec.kind == TargetKind::Type {
731            ctx.bindings.declare_shadow(name);
732            continue;
733        }
734        let value = child.child_by_field_name("value");
735        seed_binding_from_type_or_value(&name, type_node, value, ctx);
736    }
737}
738
739fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
740    if !parameter_belongs_to_callable_scope(node) {
741        return;
742    }
743    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
744        return;
745    };
746    if has_function_scope_ancestor(node) {
747        ctx.local_shadows.declare_shadow(name.clone());
748    }
749    if ctx.spec.kind == TargetKind::Type {
750        ctx.bindings.declare_shadow(name);
751        return;
752    }
753    seed_binding_from_type_or_value(&name, type_node, None, ctx);
754}
755
756fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
757    let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
758        return;
759    };
760    if has_function_scope_ancestor(node) {
761        ctx.local_shadows.declare_shadow(name.clone());
762    }
763    if ctx.spec.kind == TargetKind::Type {
764        ctx.bindings.declare_shadow(name);
765        return;
766    }
767    seed_binding_from_type_or_value(&name, type_node, None, ctx);
768}
769
770fn has_function_scope_ancestor(node: Node<'_>) -> bool {
771    let mut current = node.parent();
772    while let Some(parent) = current {
773        // Error recovery can wrap a complete namespace in a bogus outer
774        // `function_definition` (for example when an object-like namespace
775        // macro is parsed as a return type).  Declarations below that
776        // namespace are still namespace-scoped: do not let the malformed
777        // callable envelope seed them as local shadows.  A real function
778        // body is encountered before its enclosing namespace, so this keeps
779        // ordinary local binding detection unchanged.
780        if parent.kind() == "namespace_definition" {
781            return false;
782        }
783        if parent.kind() == "function_definition" {
784            // The malformed sentinel envelope is not a callable scope. Its
785            // body may contain a recovered namespace (or, in a smaller error
786            // tree, only an ERROR node standing in for that namespace), so
787            // declarations directly below it must remain namespace-scoped.
788            // Real nested functions are encountered first and still seed
789            // ordinary local bindings.
790            return !is_malformed_wrapper_function_definition(parent);
791        }
792        if parent.kind() == "lambda_expression" {
793            return true;
794        }
795        current = parent.parent();
796    }
797    false
798}
799
800fn seed_binding_from_type_or_value(
801    name: &str,
802    type_node: Option<Node<'_>>,
803    value: Option<Node<'_>>,
804    ctx: &mut ScanCtx<'_>,
805) {
806    if name.is_empty() {
807        return;
808    }
809    let resolved = type_node
810        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
811        .map(|node| {
812            let text = node_text(node, ctx.source);
813            let name = normalize_cpp_type_name(text);
814            if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
815                return CppScanBinding::from_type_name(
816                    name,
817                    Some(unit),
818                    cpp_type_text_pointer_depth(text),
819                );
820            }
821            if let Some(unit) = function_local_type_for_node(node, ctx) {
822                return CppScanBinding::from_type_name(
823                    name,
824                    unit,
825                    cpp_type_text_pointer_depth(text),
826                );
827            }
828            // Bare type names need lexical ownership before the coarse visible-name
829            // fallback (two namespaces can each declare `CopyResult`). Template
830            // references keep the specialization-aware resolver first because a
831            // component-only lexical lookup cannot rank partial specializations.
832            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
833                if cpp_template_reference_arguments(node, ctx.source).is_some() {
834                    return None;
835                }
836                match enclosing_lexical_scope_components(
837                    node,
838                    &ctx.analyzer,
839                    ctx.visibility,
840                    ctx.file,
841                    ctx.source,
842                ) {
843                    LexicalScopeResolution::Resolved(scope) => Some(scope),
844                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
845                }
846            });
847            let unit = lexical_scope
848                .as_deref()
849                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
850                .or_else(|| {
851                    match ctx
852                        .visibility
853                        .resolve_type_node_result(ctx.file, node, ctx.source)
854                    {
855                        Ok(Some(unit)) => Some(unit),
856                        Ok(None) => ctx
857                            .visibility
858                            .canonical_type_for_reference(ctx.file, &name)
859                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
860                        Err(_) => None,
861                    }
862                });
863            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
864        })
865        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
866
867    if let Some(resolved) = resolved {
868        ctx.bindings.seed_symbol(name.to_string(), resolved);
869    } else if let Some(value) = value
870        && value.kind() == "identifier"
871    {
872        ctx.bindings
873            .alias_symbol(name.to_string(), node_text(value, ctx.source));
874    } else {
875        ctx.bindings.declare_shadow(name.to_string());
876    }
877}
878
879fn resolve_seed_type_node_lexically(
880    node: Node<'_>,
881    ctx: &ScanCtx<'_>,
882    scope: &[String],
883) -> Option<CodeUnit> {
884    let (components, global) = type_reference_components(node, ctx.source)?;
885    let resolution = match scan_owner_type(ctx) {
886        Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
887            &ctx.analyzer,
888            ctx.file,
889            &components,
890            global,
891            scope,
892            target,
893        ),
894        None => ctx.visibility.resolve_type_components_lexically(
895            &ctx.analyzer,
896            ctx.file,
897            &components,
898            global,
899            scope,
900        ),
901    };
902    match resolution {
903        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
904        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
905    }
906}
907
908/// The type this scan is asking about: the owner of a member target, or the
909/// target itself when it is a type.
910///
911/// C headers routinely declare one typedef name once per branch of an
912/// `#if`/`#else` pair, for example tinycthread's `mtx_t` as a Win32 struct and
913/// as `pthread_mutex_t` (#2996). The branches cannot both be compiled, so they
914/// are alternate spellings of one name rather than competing declarations, and
915/// canonicalizing without the scan's own type identity picks between them by
916/// declaration order. That ordering can bind a receiver to the branch that does
917/// not declare the queried member, which drops every reference in the branch
918/// that does. Naming the identity keeps the target-preserving rule the forward
919/// resolver already applies (`unique_type_candidate_preserving_target`).
920fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
921    // Target-preserving lookup can reconcile logical declarations across files.
922    // A receiver must not borrow an owner from a header its caller cannot see.
923    ctx.spec
924        .owner
925        .as_ref()
926        .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
927}
928
929const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
930
931fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
932    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
933}
934
935fn infer_type_from_value_with_budget(
936    node: Node<'_>,
937    ctx: &ScanCtx<'_>,
938    remaining_call_depth: usize,
939) -> Option<CppScanBinding> {
940    match node.kind() {
941        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
942            infer_cpp_initializer_binding(
943                &ctx.analyzer,
944                ctx.visibility,
945                ctx.file,
946                ctx.source,
947                node,
948                None,
949            )
950        }
951        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
952            &ctx.analyzer,
953            ctx.visibility,
954            ctx.file,
955            ctx.source,
956            node,
957            Some(&|receiver, source| {
958                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
959            }),
960        ),
961        "initializer_list" => None,
962        "identifier" => {
963            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
964            resolved
965                .as_precise()?
966                .iter()
967                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
968                .cloned()
969        }
970        _ => {
971            let text = node_text(node, ctx.source);
972            let name = normalize_cpp_type_name(text);
973            ctx.visibility
974                .resolve_type(ctx.file, &name)
975                .map(|unit| CppScanBinding::from_unit(unit, 0))
976        }
977    }
978}
979
980/// Whether a member named `identifier` can be spelled anywhere in this file's
981/// syntax.
982///
983/// Both member scans below -- [`maybe_record_method_hit`] and
984/// [`maybe_record_member_field_hit`] -- begin by comparing the terminal name
985/// of a syntax node against the target's member name, so a file whose syntax
986/// never spells that name yields neither a proven nor an unproven hit. That
987/// makes this an exact admission test rather than an approximation: candidate
988/// discovery can drop such a file without narrowing what the query proves.
989///
990/// Only tree-sitter nodes are read. Two node kinds carry text the C++ grammar
991/// does not tokenize into identifiers -- a function-like macro's `preproc_arg`
992/// replacement, which
993/// [`maybe_record_function_macro_replacement_method_hits`] recovers by its own
994/// sentinel parse, and an `ERROR` region -- so those admit on containment
995/// instead of on a token match. A `comment` is the one subtree no scan reads,
996/// so it is skipped.
997///
998/// Defined only for a plain identifier: [`name_matches_callable`] widens the
999/// comparison for an `operator` name to the `operator` token alone, and a
1000/// destructor or conversion-function name is not spelled the way it is
1001/// recorded. [`cpp_member_is_spelled_at_references`] is the admission side of
1002/// that condition.
1003pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
1004    debug_assert!(
1005        cpp_member_is_spelled_at_references(identifier),
1006        "member admission is defined only for a plain identifier: {identifier:?}"
1007    );
1008    let mut spelled = false;
1009    walk_named_tree_preorder(root, true, |node| match node.kind() {
1010        "comment" => WalkControl::SkipChildren,
1011        "identifier"
1012        | "field_identifier"
1013        | "type_identifier"
1014        | "namespace_identifier"
1015        | "statement_identifier" => {
1016            if node_text(node, source).trim() == identifier {
1017                spelled = true;
1018                WalkControl::Break
1019            } else {
1020                WalkControl::SkipChildren
1021            }
1022        }
1023        "preproc_arg" | "ERROR" => {
1024            if node_text(node, source).contains(identifier) {
1025                spelled = true;
1026                WalkControl::Break
1027            } else {
1028                // The node's own text covers every descendant's bytes, so a
1029                // miss here is a miss for the whole subtree.
1030                WalkControl::SkipChildren
1031            }
1032        }
1033        _ => WalkControl::Continue,
1034    });
1035    spelled
1036}
1037
1038/// Whether every reference to a member named `identifier` must spell it.
1039///
1040/// An `operator` name is matched by [`name_matches_callable`] through the bare
1041/// `operator` token, and a destructor or conversion function is recorded under
1042/// a name its call sites do not write, so neither admits a spelling test.
1043pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1044    let mut characters = identifier.chars();
1045    characters
1046        .next()
1047        .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1048        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1049        && !identifier.starts_with("operator")
1050}
1051
1052fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1053    match ctx.spec.kind {
1054        TargetKind::Type => maybe_record_type_hit(node, ctx),
1055        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1056        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1057        TargetKind::Method => maybe_record_method_hit(node, ctx),
1058        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1059        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1060        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1061    }
1062}
1063
1064fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1065    if node_text(node, ctx.source) != ctx.spec.member_name {
1066        return;
1067    }
1068    if is_ordinary_macro_reference_node(node) {
1069        if ctx.visibility.macro_binding_matches_target_declaration_at(
1070            ctx.file,
1071            &ctx.spec.member_name,
1072            node.start_byte(),
1073            ctx.spec.target.source(),
1074            &ctx.target_macro_declaration_bytes,
1075        ) {
1076            *ctx.raw_match_count += 1;
1077            push_hit(node, ctx);
1078            return;
1079        }
1080        match ctx.visibility.resolve_ordinary_macro_reference(
1081            &ctx.analyzer,
1082            ctx.file,
1083            node,
1084            ctx.source,
1085        ) {
1086            OrdinaryMacroReferenceResolution::Resolved(unit)
1087                if ctx.target_group.contains(&unit) =>
1088            {
1089                *ctx.raw_match_count += 1;
1090                push_hit(node, ctx);
1091            }
1092            OrdinaryMacroReferenceResolution::Ambiguous
1093                if ctx
1094                    .visibility
1095                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1096            {
1097                *ctx.raw_match_count += 1;
1098                push_unproven_hit(node, ctx);
1099            }
1100            OrdinaryMacroReferenceResolution::Resolved(_)
1101            | OrdinaryMacroReferenceResolution::Ambiguous
1102            | OrdinaryMacroReferenceResolution::Missing => {}
1103        }
1104        return;
1105    }
1106    if !matches!(
1107        node.kind(),
1108        "identifier"
1109            | "field_identifier"
1110            | "type_identifier"
1111            | "namespace_identifier"
1112            | "preproc_arg"
1113    ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1114        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1115            && parent
1116                .child_by_field_name("name")
1117                .is_some_and(|name| same_node(name, node))
1118    }) {
1119        return;
1120    }
1121    if ctx.visibility.macro_binding_matches_target_declaration_at(
1122        ctx.file,
1123        &ctx.spec.member_name,
1124        node.start_byte(),
1125        ctx.spec.target.source(),
1126        &ctx.target_macro_declaration_bytes,
1127    ) {
1128        *ctx.raw_match_count += 1;
1129        push_hit(node, ctx);
1130    } else if ctx.visibility.macro_name_may_be_bound_at(
1131        ctx.file,
1132        &ctx.spec.member_name,
1133        node.start_byte(),
1134    ) && ctx
1135        .visibility
1136        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1137    {
1138        *ctx.raw_match_count += 1;
1139        push_unproven_hit(node, ctx);
1140    }
1141}
1142
1143fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1144    let kind = node.kind();
1145    if ctx.uses_c_semantics
1146        && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1147            .is_some()
1148    {
1149        return;
1150    }
1151    if kind == "preproc_arg" {
1152        maybe_record_object_macro_replacement_type_hits(node, ctx);
1153        return;
1154    }
1155    if let Some(type_node) = ctx
1156        .visibility
1157        .function_macro_type_argument(ctx.file, node, ctx.source)
1158    {
1159        if ctx
1160            .local_shadows
1161            .is_shadowed(node_text(type_node, ctx.source))
1162        {
1163            return;
1164        }
1165        if let LexicalTypeResolution::Resolved {
1166            unit, candidates, ..
1167        } = resolve_type_node_lexically_for_target(
1168            type_node,
1169            &ctx.analyzer,
1170            ctx.visibility,
1171            &ctx.ordinary_type_imports,
1172            ctx.file,
1173            ctx.source,
1174            &ctx.spec.target,
1175            Some(&ctx.lexical_scope_cache),
1176            ctx.recovered_sentinel_scope(type_node).as_deref(),
1177        ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1178        {
1179            *ctx.raw_match_count += 1;
1180            push_type_hit(type_node, ctx);
1181        }
1182        return;
1183    }
1184    let recovered_exported_class_base =
1185        matches!(
1186            kind,
1187            "qualified_identifier" | "scoped_type_identifier" | "template_type"
1188        ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1189    if kind == "field_declaration"
1190        && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1191    {
1192        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1193        return;
1194    }
1195    if kind == "qualified_identifier"
1196        && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1197    {
1198        // A member-pointer owner can itself end in a nested alias, as in
1199        // `type_identity<T>::type::*`. Resolving the complete owner
1200        // canonicalizes that alias to its underlying type and loses the alias
1201        // declaration that inverse lookup is targeting. Retain every
1202        // structurally proven qualifier component before asking for the
1203        // canonical owner type.
1204        if ctx
1205            .analyzer
1206            .type_alias_provider()
1207            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1208            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1209            && let Some(terminal) = owner.nodes.last().copied()
1210        {
1211            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1212                return;
1213            }
1214            if ctx.visibility.external_type_candidate_visible_in_context(
1215                &ctx.analyzer,
1216                ctx.file,
1217                &ctx.spec.target,
1218                terminal,
1219            ) || ctx
1220                .visibility
1221                .dependent_member_pointer_alias_visible_in_context(
1222                    &ctx.analyzer,
1223                    ctx.file,
1224                    &ctx.spec.target,
1225                    &owner.names,
1226                    terminal,
1227                )
1228            {
1229                *ctx.raw_match_count += 1;
1230                push_type_hit(terminal, ctx);
1231            } else if ctx
1232                .visibility
1233                .is_physically_visible(ctx.file, &ctx.spec.target)
1234                && ctx.visibility.declaration_guards_compatible_at_reference(
1235                    &ctx.analyzer,
1236                    ctx.file,
1237                    &ctx.spec.target,
1238                    terminal,
1239                )
1240            {
1241                // The structured owner and terminal identify a candidate, but
1242                // do not establish its conditional availability.
1243                *ctx.raw_match_count += 1;
1244                push_unproven_hit(terminal, ctx);
1245            }
1246            return;
1247        }
1248        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1249            *ctx.raw_match_count += 1;
1250            for scope in scopes {
1251                push_type_hit(scope, ctx);
1252            }
1253            return;
1254        }
1255        return;
1256    }
1257    if kind == "pointer_expression"
1258        && let Some(value) = qualified_callable_value(node)
1259        && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1260    {
1261        *ctx.raw_match_count += 1;
1262        match owner_hit {
1263            TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1264            TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1265        }
1266        return;
1267    }
1268    if kind == "call_expression" {
1269        maybe_record_direct_temporary_type_hit(node, ctx);
1270        return;
1271    }
1272    if !matches!(
1273        kind,
1274        "identifier"
1275            | "namespace_identifier"
1276            | "qualified_identifier"
1277            | "scoped_identifier"
1278            | "scoped_type_identifier"
1279            | "template_function"
1280            | "template_type"
1281            | "type_descriptor"
1282            | "type_identifier"
1283            | "using_declaration"
1284    ) {
1285        return;
1286    }
1287    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1288    let template_function_type = cpp_template_function_has_type_owner(node);
1289    let recovered_qualified_friend =
1290        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1291    if !recovered_type
1292        && !recovered_qualified_friend
1293        && !recovered_exported_class_base
1294        && matches!(
1295            kind,
1296            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1297        )
1298        && !type_reference_components_may_name_target(node, ctx)
1299    {
1300        return;
1301    }
1302    if ctx.ancestry.parent(node).is_some_and(|parent| {
1303        parent.kind() == "operator_cast"
1304            && parent
1305                .child_by_field_name("type")
1306                .is_some_and(|target| same_node(target, node))
1307    }) {
1308        return;
1309    }
1310    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1311        *ctx.raw_match_count += 1;
1312        push_type_hit(hit, ctx);
1313        return;
1314    }
1315    if matches!(kind, "identifier" | "template_function")
1316        && parent_if_field(node, "call_expression", "function").is_some()
1317    {
1318        return;
1319    }
1320    if kind == "using_declaration" {
1321        let (resolution, type_node) =
1322            if let Some(type_node) = using_enum_declaration_type_node(node) {
1323                (
1324                    resolve_using_enum_declaration_owner(
1325                        node,
1326                        &ctx.analyzer,
1327                        ctx.visibility,
1328                        &ctx.ordinary_type_imports,
1329                        ctx.file,
1330                        ctx.source,
1331                    ),
1332                    type_node,
1333                )
1334            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1335                (
1336                    resolve_ordinary_using_declaration_owner(
1337                        node,
1338                        &ctx.analyzer,
1339                        ctx.visibility,
1340                        ctx.file,
1341                        ctx.source,
1342                    ),
1343                    type_node,
1344                )
1345            } else {
1346                return;
1347            };
1348        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1349            && same_visible_symbol(&unit, &ctx.spec.target)
1350        {
1351            *ctx.raw_match_count += 1;
1352            push_type_hit(type_node, ctx);
1353        }
1354        return;
1355    }
1356    if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1357        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1358            || local_type_name_shadows(node, ctx)
1359        {
1360            return;
1361        }
1362        if let LexicalTypeResolution::Resolved {
1363            unit, candidates, ..
1364        } = resolve_type_node_lexically_for_target(
1365            node,
1366            &ctx.analyzer,
1367            ctx.visibility,
1368            &ctx.ordinary_type_imports,
1369            ctx.file,
1370            ctx.source,
1371            &ctx.spec.target,
1372            Some(&ctx.lexical_scope_cache),
1373            ctx.recovered_sentinel_scope(node).as_deref(),
1374        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1375        {
1376            *ctx.raw_match_count += 1;
1377            push_type_hit(node, ctx);
1378        }
1379        return;
1380    }
1381    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1382        // A missing `::` can put either the macro or the real type in the
1383        // recovered scope. Resolve both candidates against this inverse
1384        // target before choosing one: a unique match is routed through the
1385        // ordinary target-guided path, while two distinct matches are
1386        // ambiguous and must not invent a hit. With no target match, retain
1387        // the existing recovered-scope path below for its conservative
1388        // fallback behaviour.
1389        let mut matching = Vec::new();
1390        for candidate in [node, type_node] {
1391            if matching
1392                .iter()
1393                .any(|existing| same_node(*existing, candidate))
1394            {
1395                continue;
1396            }
1397            if let LexicalTypeResolution::Resolved {
1398                unit, candidates, ..
1399            } = resolve_type_node_lexically_for_target(
1400                candidate,
1401                &ctx.analyzer,
1402                ctx.visibility,
1403                &ctx.ordinary_type_imports,
1404                ctx.file,
1405                ctx.source,
1406                &ctx.spec.target,
1407                Some(&ctx.lexical_scope_cache),
1408                ctx.recovered_sentinel_scope(candidate).as_deref(),
1409            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1410            {
1411                matching.push(candidate);
1412            }
1413        }
1414        match matching.as_slice() {
1415            [candidate] if !same_node(*candidate, node) => {
1416                maybe_record_type_hit(*candidate, ctx);
1417                return;
1418            }
1419            [candidate] if same_node(*candidate, node) => {}
1420            [] => {}
1421            _ => return,
1422        }
1423    }
1424    if !recovered_type
1425        && !template_function_type
1426        && !matches!(
1427            kind,
1428            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1429        )
1430    {
1431        return;
1432    }
1433    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1434        components.len() == 1
1435            && !global
1436            && (local_type_name_shadows(node, ctx)
1437                || ctx
1438                    .analyzer
1439                    .type_alias_provider()
1440                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1441                    && nearer_type_name_shadows_structured_reference(node, ctx))
1442    }) {
1443        return;
1444    }
1445    if !local_class_target_reachable(node, ctx) {
1446        return;
1447    }
1448    if !recovered_type
1449        && !recovered_qualified_friend
1450        && !recovered_exported_class_base
1451        && ctx.ancestry.parent(node).is_some_and(|parent| {
1452            parent.kind() == "alias_declaration"
1453                && parent
1454                    .child_by_field_name("name")
1455                    .is_some_and(|name| same_node(name, node))
1456        })
1457    {
1458        return;
1459    }
1460    #[cfg(any(test, feature = "test-support"))]
1461    TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1462    if !recovered_type
1463        && !recovered_qualified_friend
1464        && !recovered_exported_class_base
1465        && matches!(kind, "qualified_identifier" | "scoped_identifier")
1466        && is_declaration_name(node)
1467        && let Some(owners) = out_of_line_member_definition_owner(
1468            &ctx.analyzer,
1469            ctx.visibility,
1470            ctx.file,
1471            ctx.source,
1472            node,
1473        )
1474    {
1475        *ctx.raw_match_count += 1;
1476        let mut matched_owner = false;
1477        for (owner_node, owner) in owners.owners {
1478            if same_visible_symbol(&owner, &ctx.spec.target) {
1479                matched_owner = true;
1480                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1481            }
1482        }
1483        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1484            for scope in scopes {
1485                push_hit(scope, ctx);
1486            }
1487        } else if !matched_owner
1488            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1489        {
1490            push_unproven_hit(scope, ctx);
1491        }
1492        return;
1493    }
1494    if !recovered_type
1495        && !recovered_qualified_friend
1496        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1497        && is_declaration_name(node)
1498        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1499    {
1500        *ctx.raw_match_count += 1;
1501        push_type_hit(owner, ctx);
1502        return;
1503    }
1504    // A qualified template-id is represented as a qualified_identifier whose
1505    // name child is the template_type. Resolve the complete qualified
1506    // reference from that inner template node below; handling the outer node
1507    // independently would either lose the qualifier or emit a duplicate,
1508    // wider hit range.
1509    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1510        && matches!(
1511            node.kind(),
1512            "qualified_identifier" | "scoped_type_identifier"
1513        )
1514        && node
1515            .child_by_field_name("name")
1516            .is_some_and(|name| name.kind() == "template_type")
1517    {
1518        return;
1519    }
1520    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1521        *ctx.raw_match_count += 1;
1522        push_unproven_hit(scope, ctx);
1523        return;
1524    }
1525    if !recovered_type
1526        && !is_nested_type_node(node)
1527        && matches!(
1528            node.kind(),
1529            "qualified_identifier" | "scoped_type_identifier"
1530        )
1531        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1532    {
1533        *ctx.raw_match_count += 1;
1534        for scope in scopes {
1535            push_type_hit(scope, ctx);
1536        }
1537        return;
1538    }
1539    if !recovered_type && is_nested_type_node(node) {
1540        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1541            *ctx.raw_match_count += 1;
1542            push_type_hit(hit, ctx);
1543            return;
1544        }
1545        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1546            *ctx.raw_match_count += 1;
1547            push_type_hit(hit, ctx);
1548            return;
1549        }
1550        // A concrete template specialization can be absent from the coarse
1551        // per-file component-name index: that index contains the primary name
1552        // while this recovered child may expose only the malformed template
1553        // leaf.  Resolve the complete enclosing template before applying the
1554        // component prefilter so an exact target candidate can prove this
1555        // reference.  Non-specialization targets retain the cheap gate.
1556        let nested_template = if node.kind() == "template_type" {
1557            Some(node)
1558        } else {
1559            ctx.ancestry.parent(node).filter(|parent| {
1560                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1561            })
1562        };
1563        // Concrete specializations need their nested template-id inspected.
1564        // An ordinary alias application does too when it is itself the scope
1565        // of a member-qualified type, because the enclosing node denotes the
1566        // member rather than the alias. In every other primary/alias shape the
1567        // enclosing structured type owns the hit range; descending would emit
1568        // a duplicate terminal subrange.
1569        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1570            let enclosing_qualified_type_owns_range =
1571                ctx.ancestry.parent(template).is_some_and(|parent| {
1572                    matches!(
1573                        parent.kind(),
1574                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1575                    ) && parent.child_by_field_name("name") == Some(template)
1576                });
1577            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1578                return false;
1579            };
1580            let Some(name) = template_reference_name_node(template) else {
1581                return false;
1582            };
1583            let alias_candidates = ctx
1584                .visibility
1585                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1586                .filter(|candidate| alias_provider.is_type_alias(candidate))
1587                .cloned()
1588                .collect::<Vec<_>>();
1589            let direct_target_alias_visible = !alias_candidates
1590                .iter()
1591                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1592                || ctx.visibility.external_type_candidate_visible_in_context(
1593                    &ctx.analyzer,
1594                    ctx.file,
1595                    &ctx.spec.target,
1596                    template,
1597                );
1598            !enclosing_qualified_type_owns_range
1599                && direct_target_alias_visible
1600                && template_reference_candidates_select_target(
1601                    template,
1602                    &alias_candidates,
1603                    &ctx.analyzer,
1604                    ctx.visibility,
1605                    ctx.file,
1606                    ctx.source,
1607                    &ctx.spec.target,
1608                )
1609        });
1610        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1611            return;
1612        }
1613        if nested_alias_qualifier {
1614            let template = nested_template.expect("a nested alias qualifier has a template node");
1615            *ctx.raw_match_count += 1;
1616            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1617                .unwrap_or_else(|| type_reference_hit_node(template));
1618            push_type_hit(hit, ctx);
1619            return;
1620        }
1621        // For concrete specializations, the visible identifier index can
1622        // already contain the exact template spelling even when lexical
1623        // resolution rejects the recovered child (the child itself has no
1624        // argument list). Select the name as structured evidence;
1625        // `push_type_hit` expands it to the complete written template-id.
1626        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1627            && let Some(template) = nested_template
1628            && template_type_component_preserves_target(
1629                template,
1630                &ctx.visibility
1631                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1632                    .cloned()
1633                    .collect::<Vec<_>>(),
1634                ctx,
1635            )
1636        {
1637            *ctx.raw_match_count += 1;
1638            let hit = template
1639                .child_by_field_name("name")
1640                .filter(|name| name.kind() == "type_identifier")
1641                .unwrap_or(template);
1642            push_type_hit(hit, ctx);
1643            return;
1644        }
1645        if let Some(template) = nested_template
1646            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1647        {
1648            *ctx.raw_match_count += 1;
1649            let hit =
1650                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1651                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1652                        template
1653                            .child_by_field_name("name")
1654                            .filter(|name| name.kind() == "type_identifier")
1655                            .unwrap_or(template)
1656                    } else {
1657                        type_reference_hit_node(template)
1658                    }
1659                });
1660            push_type_hit(hit, ctx);
1661        }
1662        return;
1663    }
1664    if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1665        let direct_target = resolve_qualified_call_target(
1666            call,
1667            node,
1668            &ctx.analyzer,
1669            ctx.visibility,
1670            &ctx.ordinary_type_imports,
1671            ctx.file,
1672            ctx.source,
1673        );
1674        if matches!(direct_target, BareCallTargetResolution::Type(_))
1675            && let LexicalTypeResolution::Resolved {
1676                unit, candidates, ..
1677            } = resolve_type_node_lexically_for_target(
1678                node,
1679                &ctx.analyzer,
1680                ctx.visibility,
1681                &ctx.ordinary_type_imports,
1682                ctx.file,
1683                ctx.source,
1684                &ctx.spec.target,
1685                Some(&ctx.lexical_scope_cache),
1686                ctx.recovered_sentinel_scope(node).as_deref(),
1687            )
1688            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1689        {
1690            *ctx.raw_match_count += 1;
1691            push_resolved_type_hit(node, &candidates, ctx);
1692        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1693            *ctx.raw_match_count += 1;
1694            for scope in scopes {
1695                push_type_hit(scope, ctx);
1696            }
1697        } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1698            *ctx.raw_match_count += 1;
1699            match owner_hit {
1700                TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1701                TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1702                    push_unproven_hit(scope, ctx)
1703                }
1704            }
1705        }
1706        return;
1707    }
1708    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1709        *ctx.raw_match_count += 1;
1710        if proven {
1711            push_type_hit(hit, ctx);
1712        } else {
1713            push_unproven_hit(hit, ctx);
1714        }
1715        return;
1716    }
1717    if !recovered_type
1718        && !recovered_qualified_friend
1719        && !recovered_exported_class_base
1720        && is_declaration_name(node)
1721    {
1722        let mut matched_owner = false;
1723        if let Some(owners) = out_of_line_member_definition_owner(
1724            &ctx.analyzer,
1725            ctx.visibility,
1726            ctx.file,
1727            ctx.source,
1728            node,
1729        ) {
1730            for (owner_node, owner) in owners.owners {
1731                if same_visible_symbol(&owner, &ctx.spec.target) {
1732                    matched_owner = true;
1733                    *ctx.raw_match_count += 1;
1734                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1735                }
1736            }
1737        }
1738        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1739            *ctx.raw_match_count += 1;
1740            for scope in scopes {
1741                push_hit(scope, ctx);
1742            }
1743        } else if !matched_owner
1744            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1745        {
1746            *ctx.raw_match_count += 1;
1747            push_unproven_hit(scope, ctx);
1748        }
1749        return;
1750    }
1751    let hit_node = node;
1752    let text = node_text(hit_node, ctx.source);
1753    let type_resolution = if hit_node.kind() == "template_type"
1754        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1755    {
1756        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1757            resolve_type_node_lexically_for_target(
1758                hit_node,
1759                &ctx.analyzer,
1760                ctx.visibility,
1761                &ctx.ordinary_type_imports,
1762                ctx.file,
1763                ctx.source,
1764                &ctx.spec.target,
1765                Some(&ctx.lexical_scope_cache),
1766                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1767            )
1768        })
1769    } else {
1770        resolve_type_node_lexically_for_target(
1771            hit_node,
1772            &ctx.analyzer,
1773            ctx.visibility,
1774            &ctx.ordinary_type_imports,
1775            ctx.file,
1776            ctx.source,
1777            &ctx.spec.target,
1778            Some(&ctx.lexical_scope_cache),
1779            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1780        )
1781    };
1782    // When the qualifier's template arguments decide the owner, the resolution
1783    // above already looked the member up in that owner. The target-guided
1784    // fallbacks below read the qualifier without its arguments and would
1785    // prove the primary template's member instead, so they do not run; an
1786    // owner the arguments cannot decide leaves the reference unproven
1787    // (#6902).
1788    match select_qualifier_owner(
1789        node,
1790        is_globally_qualified_cpp_name(node),
1791        &ctx.analyzer,
1792        ctx.visibility,
1793        &ctx.ordinary_type_imports,
1794        ctx.file,
1795        ctx.source,
1796    ) {
1797        QualifierOwnerSelection::Unchanged => {}
1798        QualifierOwnerSelection::Selected(_) => {
1799            if !matches!(
1800                &type_resolution,
1801                LexicalTypeResolution::Resolved { unit, candidates, .. }
1802                    if type_resolution_matches_target(node, unit, candidates, ctx)
1803            ) {
1804                return;
1805            }
1806        }
1807        QualifierOwnerSelection::Ambiguous => {
1808            // Only the member the terminal names depends on the undecided
1809            // owner; a qualifier or alias target keeps its ordinary handling.
1810            if let Some(terminal) = cpp_name_component_nodes(node)
1811                .and_then(|components| components.last().copied())
1812                .filter(|terminal| node_text(*terminal, ctx.source) == ctx.spec.target.identifier())
1813            {
1814                *ctx.raw_match_count += 1;
1815                push_unproven_hit(terminal, ctx);
1816                return;
1817            }
1818        }
1819    }
1820    match type_resolution {
1821        LexicalTypeResolution::Resolved {
1822            unit, candidates, ..
1823        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1824            *ctx.raw_match_count += 1;
1825            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1826                for scope in scopes {
1827                    push_type_hit(scope, ctx);
1828                }
1829            } else {
1830                push_resolved_type_hit(hit_node, &candidates, ctx);
1831            }
1832            return;
1833        }
1834        LexicalTypeResolution::Resolved {
1835            unit: _,
1836            candidates,
1837            ..
1838        } => {
1839            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1840                *ctx.raw_match_count += 1;
1841                for scope in scopes {
1842                    push_type_hit(scope, ctx);
1843                }
1844            } else if let Some(hit) =
1845                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1846            {
1847                *ctx.raw_match_count += 1;
1848                push_unproven_hit(hit, ctx);
1849            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1850                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1851            {
1852                *ctx.raw_match_count += 1;
1853                push_type_hit(leaf, ctx);
1854            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1855                *ctx.raw_match_count += 1;
1856                push_unproven_hit(leaf, ctx);
1857            }
1858            return;
1859        }
1860        LexicalTypeResolution::Ambiguous => {
1861            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1862                *ctx.raw_match_count += 1;
1863                for scope in scopes {
1864                    push_type_hit(scope, ctx);
1865                }
1866            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1867                *ctx.raw_match_count += 1;
1868                push_unproven_hit(leaf, ctx);
1869            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1870                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1871                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1872            {
1873                *ctx.raw_match_count += 1;
1874                push_type_hit(leaf, ctx);
1875            } else if guarded_alias_alternatives_include_target(hit_node, ctx) {
1876                *ctx.raw_match_count += 1;
1877                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1878            }
1879            return;
1880        }
1881        LexicalTypeResolution::Missing => {
1882            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1883                &ctx.analyzer,
1884                ctx.file,
1885                hit_node,
1886                ctx.source,
1887            ) && same_visible_symbol(&unit, &ctx.spec.target)
1888            {
1889                *ctx.raw_match_count += 1;
1890                push_type_hit(hit_node, ctx);
1891                return;
1892            }
1893            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1894                *ctx.raw_match_count += 1;
1895                push_unproven_hit(leaf, ctx);
1896                return;
1897            }
1898            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1899                *ctx.raw_match_count += 1;
1900                push_unproven_hit(leaf, ctx);
1901                return;
1902            }
1903            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1904                *ctx.raw_match_count += 1;
1905                push_type_hit(leaf, ctx);
1906                return;
1907            }
1908            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1909                hit_node,
1910                &ctx.analyzer,
1911                ctx.visibility,
1912                ctx.file,
1913                ctx.source,
1914                &ctx.spec.target,
1915            );
1916            let raw_matches = matches!(
1917                raw_resolution,
1918                LexicalTypeResolution::Resolved {
1919                    ref unit,
1920                    ref candidates,
1921                    ..
1922                } if type_resolution_identifies_unit_target(
1923                    hit_node,
1924                    unit,
1925                    candidates,
1926                    &ctx.spec.target,
1927                    ctx,
1928                )
1929            );
1930            if raw_matches
1931                || type_node_has_exact_target_identity_without_visibility(
1932                    hit_node,
1933                    &ctx.analyzer,
1934                    ctx.visibility,
1935                    ctx.file,
1936                    ctx.source,
1937                    &ctx.spec.target,
1938                )
1939            {
1940                *ctx.raw_match_count += 1;
1941                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1942                return;
1943            }
1944        }
1945    }
1946    // A class-owned alias can be qualified by an owner made visible through a
1947    // namespace import, including an import in an earlier reopening of the
1948    // same unnamed namespace. Resolve that owner before requiring an
1949    // enclosing class for inherited nested-type lookup.
1950    if ctx
1951        .visibility
1952        .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1953    {
1954        *ctx.raw_match_count += 1;
1955        push_type_hit(type_reference_hit_node(hit_node), ctx);
1956        return;
1957    }
1958    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1959        *ctx.raw_match_count += 1;
1960        for scope in scopes {
1961            push_type_hit(scope, ctx);
1962        }
1963        return;
1964    }
1965    if !name_mentions(text, &ctx.spec.member_name) {
1966        return;
1967    }
1968    *ctx.raw_match_count += 1;
1969    if !ctx.visibility.external_type_candidate_visible_in_context(
1970        &ctx.analyzer,
1971        ctx.file,
1972        &ctx.spec.target,
1973        hit_node,
1974    ) {
1975        let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1976        if type_reference_resolves_away_from_target(unproven, ctx) {
1977            return;
1978        }
1979        push_unproven_hit(unproven, ctx);
1980    }
1981}
1982
1983fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1984    for reference in object_macro_replacement_type_references(node, ctx.source) {
1985        for component_count in 1..=reference.components.len() {
1986            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1987                node,
1988                &reference.components[..component_count],
1989                reference.global,
1990                &ctx.analyzer,
1991                ctx.visibility,
1992                &ctx.ordinary_type_imports,
1993                ctx.file,
1994                ctx.source,
1995                &ctx.spec.target,
1996                false,
1997                Some(&ctx.lexical_scope_cache),
1998            );
1999            let matches_target = match resolution {
2000                LexicalTypeResolution::Resolved {
2001                    unit, candidates, ..
2002                } => {
2003                    same_visible_symbol(&unit, &ctx.spec.target)
2004                        || candidates
2005                            .iter()
2006                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2007                }
2008                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
2009                    &ctx.analyzer,
2010                    ctx.visibility,
2011                    ctx.file,
2012                    &reference.components[..component_count],
2013                )
2014                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
2015                LexicalTypeResolution::Ambiguous => false,
2016            };
2017            if !matches_target {
2018                continue;
2019            }
2020            let range = &reference.component_ranges[component_count - 1];
2021            *ctx.raw_match_count += 1;
2022            push_type_hit_range(node, range.start, range.end, ctx);
2023        }
2024    }
2025}
2026
2027fn target_guided_compatible_foreign_import_type_leaf<'tree>(
2028    node: Node<'tree>,
2029    ctx: &ScanCtx<'_>,
2030) -> Option<Node<'tree>> {
2031    let (components, global) = type_reference_components(node, ctx.source)?;
2032    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
2033        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
2034            node,
2035            &ctx.analyzer,
2036            ctx.visibility,
2037            ctx.file,
2038            ctx.source,
2039            false,
2040            false,
2041            Some(&ctx.lexical_scope_cache),
2042        ) {
2043            LexicalScopeResolution::Resolved(scope) => Some(scope),
2044            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
2045        }
2046    })?;
2047    let OrdinaryTypeImportResolution::Resolved { target, .. } =
2048        compatible_foreign_type_import_resolution(
2049            node,
2050            &components,
2051            global,
2052            &ctx.analyzer,
2053            ctx.visibility,
2054            &ctx.ordinary_type_imports,
2055            ctx.file,
2056            ctx.source,
2057            &lexical_scope,
2058            Some(&ctx.spec.target),
2059        )
2060    else {
2061        return None;
2062    };
2063    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2064}
2065
2066/// Resolve `Owner::Nested` when complete type lookup cannot express the owner
2067/// as its first qualified component. This occurs when a using-directive
2068/// imports `Owner` and when inherited-type lookup injects it. Resolve the
2069/// owner prefix independently, then return the terminal node after that owner
2070/// proves the target's direct nested declaration; hit insertion expands it to
2071/// the complete written type.
2072fn target_guided_nested_type_terminal_hit<'tree>(
2073    node: Node<'tree>,
2074    ctx: &ScanCtx<'_>,
2075) -> Option<Node<'tree>> {
2076    // Returns a node of the caller's tree lifetime, which the context-scoped
2077    // ancestry index cannot name; this is one question per call.
2078    let qualified = node.parent().filter(|parent| {
2079        matches!(
2080            parent.kind(),
2081            "qualified_identifier" | "scoped_type_identifier"
2082        ) && parent.child_by_field_name("name") == Some(node)
2083    })?;
2084    let mut complete = qualified;
2085    while let Some(parent) = complete.parent().filter(|parent| {
2086        matches!(
2087            parent.kind(),
2088            "qualified_identifier" | "scoped_type_identifier"
2089        )
2090    }) {
2091        complete = parent;
2092    }
2093    let owner = qualified_owner_components(complete, ctx.source)?;
2094
2095    if let LexicalTypeResolution::Resolved {
2096        unit, candidates, ..
2097    } = resolve_type_node_lexically_for_target(
2098        complete,
2099        &ctx.analyzer,
2100        ctx.visibility,
2101        &ctx.ordinary_type_imports,
2102        ctx.file,
2103        ctx.source,
2104        &ctx.spec.target,
2105        Some(&ctx.lexical_scope_cache),
2106        ctx.recovered_sentinel_scope(complete).as_deref(),
2107    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2108        && ctx
2109            .analyzer
2110            .parent_of(&ctx.spec.target)
2111            .is_some_and(|parent| parent.is_class())
2112        && !ctx
2113            .analyzer
2114            .type_alias_provider()
2115            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2116        && is_namespace_scope_type_reference(complete)
2117        && namespace_import_can_name_nested_target(complete, ctx)
2118    {
2119        return Some(node);
2120    }
2121    // The owner-spelling paths below compare the qualifier's names without
2122    // its template arguments, which only names the right owner when those
2123    // arguments select the primary template (#6902).
2124    if !matches!(
2125        select_qualifier_owner(
2126            complete,
2127            owner.global,
2128            &ctx.analyzer,
2129            ctx.visibility,
2130            &ctx.ordinary_type_imports,
2131            ctx.file,
2132            ctx.source,
2133        ),
2134        QualifierOwnerSelection::Unchanged
2135    ) {
2136        return None;
2137    }
2138
2139    let target_owner = ctx
2140        .analyzer
2141        .parent_of(&ctx.spec.target)
2142        .filter(CodeUnit::is_class);
2143    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2144        let target_components = canonical_cpp_scope_components(target_owner);
2145        if owner.global {
2146            target_components == owner.names
2147        } else {
2148            target_components.ends_with(&owner.names)
2149        }
2150    });
2151    let target_member_visible = |target_owner: &CodeUnit| {
2152        ctx.visibility
2153            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2154            .into_iter()
2155            .any(|candidate| {
2156                same_visible_symbol(candidate, &ctx.spec.target)
2157                    && (ctx
2158                        .visibility
2159                        .external_type_candidate_guard_compatible_in_context(
2160                            &ctx.analyzer,
2161                            ctx.file,
2162                            candidate,
2163                            complete,
2164                        )
2165                        || (ctx
2166                            .visibility
2167                            .is_exhaustive_same_fqn_type_declaration_family(
2168                                &ctx.analyzer,
2169                                ctx.file,
2170                                candidate,
2171                            )
2172                            && ctx.visibility.external_type_candidate_visible_in_context(
2173                                &ctx.analyzer,
2174                                ctx.file,
2175                                candidate,
2176                                complete,
2177                            )))
2178            })
2179    };
2180
2181    if owner_spelling_can_name_target
2182        && (is_namespace_scope_type_reference(complete)
2183            || (is_compound_type_reference(complete)
2184                && namespace_import_can_name_nested_target(complete, ctx)))
2185        && let Some(target_owner) = target_owner.as_ref()
2186        && let LexicalTypeResolution::Resolved { unit, .. } =
2187            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2188                complete,
2189                &owner.names,
2190                owner.global,
2191                &ctx.analyzer,
2192                ctx.visibility,
2193                &ctx.ordinary_type_imports,
2194                ctx.file,
2195                ctx.source,
2196                Some(&ctx.lexical_scope_cache),
2197            )
2198        && ctx
2199            .visibility
2200            .same_template_owner_identity(&unit, target_owner)
2201        && target_member_visible(target_owner)
2202    {
2203        return Some(node);
2204    }
2205
2206    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2207    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2208    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2209        &ctx.analyzer,
2210        ctx.file,
2211        &owner.names,
2212        owner.global,
2213        &lexical_scope,
2214    );
2215    let owner_unit = match owner_resolution {
2216        LexicalTypeResolution::Resolved { unit, .. } => unit,
2217        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2218            ctx.visibility.inherited_injected_class_owner(
2219                &ctx.analyzer,
2220                ctx.file,
2221                &enclosing_owner,
2222                owner.names.first()?,
2223            )?
2224        }
2225        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2226    };
2227    let name = node_text(node, ctx.source);
2228    ctx.visibility
2229        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2230        .into_iter()
2231        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2232        .then_some(complete)
2233}
2234
2235fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2236    // Namespace-scope nested classes need a terminal edge when the complete
2237    // qualified node is the only structured evidence (as in #2213). In
2238    // parameters, fields, and callable bodies the enclosing type node already
2239    // owns the exact range; adding its terminal would duplicate that edge.
2240    let mut current = node.parent();
2241    while let Some(parent) = current {
2242        match parent.kind() {
2243            "call_expression" | "new_expression" => return true,
2244            "parameter_declaration"
2245            | "optional_parameter_declaration"
2246            | "field_declaration"
2247            | "function_definition"
2248            | "function_declarator"
2249            | "class_specifier"
2250            | "struct_specifier"
2251            | "union_specifier"
2252            | "compound_statement" => return false,
2253            "declaration" => {
2254                let mut cursor = parent.walk();
2255                if parent
2256                    .named_children(&mut cursor)
2257                    .any(|child| child.kind() == "function_declarator")
2258                {
2259                    return false;
2260                }
2261            }
2262            _ => {}
2263        }
2264        current = parent.parent();
2265    }
2266    true
2267}
2268
2269fn is_compound_type_reference(node: Node<'_>) -> bool {
2270    let mut current = node.parent();
2271    while let Some(parent) = current {
2272        match parent.kind() {
2273            "compound_statement" => return true,
2274            "namespace_definition" | "translation_unit" => return false,
2275            _ => current = parent.parent(),
2276        }
2277    }
2278    false
2279}
2280
2281fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2282    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2283        return false;
2284    };
2285    effective_using_bindings_for_name(
2286        ctx.visibility,
2287        &ctx.ordinary_type_imports,
2288        ctx.file,
2289        node,
2290        ctx.source,
2291        target_owner.identifier(),
2292    )
2293    .iter()
2294    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2295}
2296
2297/// Recover the class-template qualifier in a dependent leading return type of
2298/// an out-of-line member definition.
2299///
2300/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
2301/// `Owner`: one in the return type and one in the callable declarator. The
2302/// latter already has authoritative indexed owner resolution. Reuse that
2303/// structured owner evidence for the former when both template names agree;
2304/// resolving `Owner<T>::Alias` as one type would instead select terminal
2305/// `Alias` and discard the qualifier.
2306fn out_of_line_dependent_return_template_owner<'tree>(
2307    node: Node<'tree>,
2308    ctx: &ScanCtx<'_>,
2309) -> Option<Node<'tree>> {
2310    if node.kind() != "template_type" {
2311        return None;
2312    }
2313    let template_name = template_reference_name_node(node)?;
2314    let mut scope = node;
2315    while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2316        matches!(
2317            parent.kind(),
2318            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2319        ) && parent.child_by_field_name("name") == Some(scope)
2320    }) {
2321        scope = parent;
2322    }
2323    let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2324        matches!(
2325            parent.kind(),
2326            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2327        ) && parent.child_by_field_name("scope") == Some(scope)
2328    })?;
2329    let mut function = ctx.ancestry.parent(qualified);
2330    let function = loop {
2331        let candidate = function?;
2332        if candidate.kind() == "function_definition" {
2333            break candidate;
2334        }
2335        function = ctx.ancestry.parent(candidate);
2336    };
2337    let return_type = function.child_by_field_name("type")?;
2338    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2339        return None;
2340    }
2341    let declarator_name = function
2342        .child_by_field_name("declarator")
2343        .and_then(declarator_name_node)?;
2344    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2345        return None;
2346    }
2347    let resolved_owner_matches = out_of_line_member_definition_owner(
2348        &ctx.analyzer,
2349        ctx.visibility,
2350        ctx.file,
2351        ctx.source,
2352        declarator_name,
2353    )
2354    .is_some_and(|owners| {
2355        owners
2356            .owners
2357            .iter()
2358            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2359    });
2360    let indexed_owner_matches =
2361        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2362    let target_guided_owner_matches =
2363        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2364    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2365        .then_some(template_name)
2366}
2367
2368/// Use the indexed callable identity when a malformed namespace sentinel
2369/// prevents lexical lookup of an out-of-line template owner.
2370fn indexed_out_of_line_template_owner_hit<'tree>(
2371    node: Node<'tree>,
2372    ctx: &ScanCtx<'_>,
2373) -> Option<Node<'tree>> {
2374    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2375        || !is_declaration_name(node)
2376    {
2377        return None;
2378    }
2379    let mut function = ctx.ancestry.parent(node);
2380    let function = loop {
2381        let candidate = function?;
2382        if candidate.kind() == "function_definition" {
2383            break candidate;
2384        }
2385        function = ctx.ancestry.parent(candidate);
2386    };
2387    if function
2388        .child_by_field_name("declarator")
2389        .and_then(declarator_name_node)
2390        != Some(node)
2391    {
2392        return None;
2393    }
2394    let target = physically_visible_type_target(ctx)?;
2395    let qualified = qualified_owner_components(node, ctx.source)?;
2396    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2397        return None;
2398    }
2399    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2400        != canonical_cpp_scope_components(target)
2401    {
2402        return None;
2403    }
2404    let owner = qualified.nodes.last().copied()?;
2405    let template = if owner.kind() == "template_type" {
2406        owner
2407    } else {
2408        owner
2409            .parent()
2410            .filter(|parent| parent.kind() == "template_type")?
2411    };
2412    template_reference_name_node(template)
2413}
2414
2415fn push_guarded_owner_hit(
2416    owner_node: Node<'_>,
2417    owner: &CodeUnit,
2418    reference: Node<'_>,
2419    ctx: &mut ScanCtx<'_>,
2420) {
2421    if ctx
2422        .visibility
2423        .external_type_candidate_guard_compatible_in_context(
2424            &ctx.analyzer,
2425            ctx.file,
2426            owner,
2427            reference,
2428        )
2429    {
2430        push_hit(owner_node, ctx);
2431    } else {
2432        push_unproven_hit(owner_node, ctx);
2433    }
2434}
2435
2436/// Preserve a direct template-alias reference when a macro namespace sentinel
2437/// makes tree-sitter drop the first source path component.
2438fn target_guided_alias_template_reference<'tree>(
2439    node: Node<'tree>,
2440    ctx: &ScanCtx<'_>,
2441) -> Option<(Node<'tree>, bool)> {
2442    let alias_provider = ctx.analyzer.type_alias_provider()?;
2443    if !matches!(
2444        node.kind(),
2445        "qualified_identifier" | "scoped_type_identifier"
2446    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2447    {
2448        return None;
2449    }
2450    cpp_template_reference_arguments(node, ctx.source)?;
2451    let (components, global) = type_reference_components(node, ctx.source)?;
2452    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2453        return None;
2454    }
2455    let target = physically_visible_type_target(ctx)?;
2456    let target_components = canonical_cpp_scope_components(target);
2457    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2458    let path_matches = if global {
2459        components == target_components
2460            || (!parser_namespace.is_empty()
2461                && target_components.starts_with(&parser_namespace)
2462                && target_components[parser_namespace.len()..] == components)
2463    } else {
2464        let lexical_scope = match enclosing_lexical_scope_components(
2465            node,
2466            &ctx.analyzer,
2467            ctx.visibility,
2468            ctx.file,
2469            ctx.source,
2470        ) {
2471            LexicalScopeResolution::Resolved(scope) => scope,
2472            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2473        };
2474        lexical_component_tiers(&components, false, &lexical_scope)
2475            .any(|scope| scope == target_components)
2476    };
2477    let scoped_candidates = ctx
2478        .visibility
2479        .visible_identifier_candidates(ctx.file, target.identifier())
2480        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2481        .collect::<Vec<_>>();
2482    if !path_matches
2483        || scoped_candidates.is_empty()
2484        || scoped_candidates
2485            .iter()
2486            .any(|candidate| !same_visible_symbol(candidate, target))
2487        || !ctx.visibility.structured_alias_primary_preserves_target(
2488            &ctx.analyzer,
2489            ctx.file,
2490            target,
2491            target,
2492        )
2493    {
2494        return None;
2495    }
2496    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2497        &ctx.analyzer,
2498        ctx.file,
2499        target,
2500        node,
2501    );
2502    Some((node, proven))
2503}
2504
2505/// Tree-sitter can split a macro-qualified member return type into a phantom
2506/// field followed by the real function definition.  The declaration visitor
2507/// discards that phantom field, but its declarator token remains a semantic
2508/// type reference. Resolve it from the recovered or indexed class scope so an
2509/// enclosing-class alias remains distinguishable from same-spelled siblings.
2510fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2511    let name = node_text(return_type, ctx.source);
2512    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2513        return;
2514    }
2515    if physically_visible_type_target(ctx).is_some()
2516        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2517        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2518        && ctx.visibility.external_type_candidate_visible_in_context(
2519            &ctx.analyzer,
2520            ctx.file,
2521            &ctx.spec.target,
2522            return_type,
2523        )
2524    {
2525        *ctx.raw_match_count += 1;
2526        push_type_hit(return_type, ctx);
2527        return;
2528    }
2529    let Some(scope) = ctx
2530        .recovered_sentinel_scope(return_type)
2531        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2532    else {
2533        return;
2534    };
2535    let components = [name.to_string()];
2536    let resolution = ctx.visibility.resolve_type_components_lexically(
2537        &ctx.analyzer,
2538        ctx.file,
2539        &components,
2540        false,
2541        &scope,
2542    );
2543    if let LexicalTypeResolution::Resolved {
2544        unit, candidates, ..
2545    } = resolution
2546        && (same_visible_symbol(&unit, &ctx.spec.target)
2547            || candidates
2548                .iter()
2549                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2550    {
2551        *ctx.raw_match_count += 1;
2552        push_type_hit(return_type, ctx);
2553    }
2554}
2555
2556/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2557/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2558/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2559/// than a type node; ordinary type-reference traversal therefore skips it.
2560fn member_pointer_owner_components<'tree>(
2561    node: Node<'tree>,
2562    source: &str,
2563) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2564    if node.kind() != "qualified_identifier" {
2565        return None;
2566    }
2567    let declarator = node.child_by_field_name("name")?;
2568    // brokk-tree-sitter-cpp 0.24.7 names a member pointer with the pointer
2569    // declarator of its context: `pointer_declarator` in declarations,
2570    // parameters and fields, `pointer_type_declarator` in typedefs, and
2571    // `abstract_pointer_declarator` in `R (Owner::*)(...)`.
2572    if !matches!(
2573        declarator.kind(),
2574        "pointer_declarator" | "pointer_type_declarator" | "abstract_pointer_declarator"
2575    ) {
2576        return None;
2577    }
2578    let scope = node.child_by_field_name("scope")?;
2579    // Keep this check entirely structural.  C and C++ share the
2580    // `qualified_identifier` node shape for some recovered declarations, but
2581    // only the C++ member-pointer form has an actual `::` grammar child
2582    // between the owner scope and pointer declarator.  Looking at the source
2583    // slice here would make a recovered C declarator look like a C++ owner
2584    // merely because its bytes happen to contain the same punctuation.
2585    let mut saw_scope = false;
2586    let has_scope_separator = (0..node.child_count()).any(|index| {
2587        let Some(child) = node.child(index) else {
2588            return false;
2589        };
2590        if same_node(child, scope) {
2591            saw_scope = true;
2592            return false;
2593        }
2594        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2595    });
2596    if !has_scope_separator {
2597        return None;
2598    }
2599    let mut nodes = cpp_name_component_nodes(scope)?;
2600    let mut outer = node;
2601    while let Some(parent) = outer.parent()
2602        && parent.kind() == "qualified_identifier"
2603        && parent.child_by_field_name("name") == Some(outer)
2604    {
2605        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2606        prefix.append(&mut nodes);
2607        nodes = prefix;
2608        outer = parent;
2609    }
2610    let names = nodes
2611        .iter()
2612        .map(|component| node_text(*component, source).to_string())
2613        .collect();
2614    Some((
2615        QualifiedOwnerComponents {
2616            nodes,
2617            names,
2618            global: is_globally_qualified_cpp_name(outer),
2619        },
2620        outer,
2621    ))
2622}
2623
2624fn member_pointer_alias_owner_prefix_matches(
2625    node: Node<'_>,
2626    owner: &QualifiedOwnerComponents<'_>,
2627    ctx: &ScanCtx<'_>,
2628) -> bool {
2629    let Some((_, owner_prefix)) = owner.names.split_last() else {
2630        return false;
2631    };
2632    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2633        return false;
2634    };
2635    let recovered_scope = ctx.recovered_sentinel_scope(node);
2636    let resolution = if let Some(recovered_scope) = recovered_scope {
2637        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2638            node,
2639            owner_prefix,
2640            owner.global,
2641            &ctx.analyzer,
2642            ctx.visibility,
2643            &ctx.ordinary_type_imports,
2644            ctx.file,
2645            ctx.source,
2646            &parent,
2647            false,
2648            &recovered_scope,
2649        )
2650    } else {
2651        resolve_type_components_lexically_at_for_target_with_scope_cache(
2652            node,
2653            owner_prefix,
2654            owner.global,
2655            &ctx.analyzer,
2656            ctx.visibility,
2657            &ctx.ordinary_type_imports,
2658            ctx.file,
2659            ctx.source,
2660            &parent,
2661            false,
2662            Some(&ctx.lexical_scope_cache),
2663        )
2664    };
2665    let LexicalTypeResolution::Resolved {
2666        unit, candidates, ..
2667    } = resolution
2668    else {
2669        return false;
2670    };
2671    same_member_pointer_owner_identity(&unit, &parent)
2672        || candidates
2673            .iter()
2674            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2675}
2676
2677fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2678    same_visible_symbol(left, right)
2679        || (left.kind() == right.kind()
2680            && left.fq_name() == right.fq_name()
2681            && left.source() == right.source())
2682}
2683
2684/// Resolve a template type nested in a qualified identifier against a concrete
2685/// type target. The target-guided lexical path intentionally applies a
2686/// structured candidate prefilter; that prefilter cannot see a partial
2687/// specialization until the template arguments have selected it. Resolve the
2688/// complete qualified primary first, then apply the parsed arguments and
2689/// retain the result only when it is the requested target.
2690fn resolve_nested_template_type_for_target(
2691    node: Node<'_>,
2692    ctx: &ScanCtx<'_>,
2693) -> Option<LexicalTypeResolution> {
2694    let reference_node = node
2695        .parent()
2696        .filter(|parent| {
2697            parent.kind() == "qualified_identifier"
2698                && parent.child_by_field_name("name") == Some(node)
2699        })
2700        .unwrap_or(node);
2701    let target_resolution = resolve_type_node_lexically_for_target(
2702        reference_node,
2703        &ctx.analyzer,
2704        ctx.visibility,
2705        &ctx.ordinary_type_imports,
2706        ctx.file,
2707        ctx.source,
2708        &ctx.spec.target,
2709        Some(&ctx.lexical_scope_cache),
2710        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2711    );
2712    if let LexicalTypeResolution::Resolved {
2713        unit, candidates, ..
2714    } = target_resolution
2715        && template_reference_candidates_select_target(
2716            reference_node,
2717            &candidates,
2718            &ctx.analyzer,
2719            ctx.visibility,
2720            ctx.file,
2721            ctx.source,
2722            &ctx.spec.target,
2723        )
2724    {
2725        return Some(LexicalTypeResolution::Resolved {
2726            unit,
2727            components: Vec::new(),
2728            candidates,
2729        });
2730    }
2731
2732    let normal_resolution = resolve_type_node_lexically(
2733        reference_node,
2734        &ctx.analyzer,
2735        ctx.visibility,
2736        &ctx.ordinary_type_imports,
2737        ctx.file,
2738        ctx.source,
2739    );
2740    let LexicalTypeResolution::Resolved {
2741        unit,
2742        components,
2743        candidates,
2744    } = normal_resolution
2745    else {
2746        return None;
2747    };
2748    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2749    let specialized = ctx
2750        .visibility
2751        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2752        .ok()
2753        .unwrap_or(unit);
2754    (same_visible_symbol(&specialized, &ctx.spec.target)
2755        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2756    .then_some(LexicalTypeResolution::Resolved {
2757        unit: specialized,
2758        components,
2759        candidates,
2760    })
2761}
2762
2763fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2764    let mut reference = node;
2765    while let Some(parent) = ctx.ancestry.parent(reference) {
2766        let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2767            && parent.child_by_field_name("name") == Some(reference)
2768            || matches!(
2769                parent.kind(),
2770                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2771            ) && (parent.child_by_field_name("scope") == Some(reference)
2772                || parent.child_by_field_name("name") == Some(reference));
2773        if !owns_component {
2774            break;
2775        }
2776        reference = parent;
2777    }
2778    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2779        return false;
2780    };
2781    if !global
2782        && components.len() > 1
2783        && let Some(alias_type) = local_receiver_alias_type_node(reference, &components[0], ctx)
2784    {
2785        let Ok(Some(owner)) = resolve_receiver_type_node(alias_type, ctx) else {
2786            return false;
2787        };
2788        let mut redirected = canonical_cpp_scope_components(&owner);
2789        redirected.extend_from_slice(&components[1..]);
2790        return type_reference_component_list_may_name_target(
2791            &redirected,
2792            true,
2793            &ctx.analyzer,
2794            ctx.visibility,
2795            ctx.file,
2796            &ctx.spec.target,
2797            &ctx.type_reference_component_names,
2798        );
2799    }
2800    type_reference_component_list_may_name_target(
2801        &components,
2802        global,
2803        &ctx.analyzer,
2804        ctx.visibility,
2805        ctx.file,
2806        &ctx.spec.target,
2807        &ctx.type_reference_component_names,
2808    )
2809}
2810
2811fn type_reference_component_list_may_name_target(
2812    components: &[String],
2813    global: bool,
2814    analyzer: &CppGraphSource<'_>,
2815    visibility: &VisibilityIndex<'_>,
2816    file: &ProjectFile,
2817    target: &CodeUnit,
2818    reference_component_names: &HashSet<String>,
2819) -> bool {
2820    if components.iter().any(|component| {
2821        visibility.type_reference_component_directly_names_target(component, target)
2822    }) {
2823        return true;
2824    }
2825    if components.iter().any(|component| {
2826        visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2827    }) {
2828        return true;
2829    }
2830    if components.len() > 1 {
2831        return visibility.qualified_alias_reference_may_reach_target(
2832            analyzer, file, components, global, target,
2833        );
2834    }
2835    components
2836        .iter()
2837        .any(|component| reference_component_names.contains(component))
2838}
2839
2840/// Fast structured admission before a type scan builds its parent and lexical
2841/// indexes. Most inverse candidates merely include the target's header and do
2842/// not contain a reference spelling; the full stateful scan has no work in
2843/// those files. Only maximal grammar type names are tested so a common nested
2844/// alias terminal such as `type` is considered together with its owner.
2845fn file_may_reference_type_target(
2846    root: Node<'_>,
2847    source: &str,
2848    analyzer: &CppGraphSource<'_>,
2849    visibility: &VisibilityIndex<'_>,
2850    file: &ProjectFile,
2851    target: &CodeUnit,
2852    reference_component_names: &HashSet<String>,
2853) -> bool {
2854    visit_file_type_reference_spellings(root, source, |components, global| {
2855        type_reference_component_list_may_name_target(
2856            components,
2857            global,
2858            analyzer,
2859            visibility,
2860            file,
2861            target,
2862            reference_component_names,
2863        )
2864    })
2865}
2866
2867fn visit_file_type_reference_spellings(
2868    root: Node<'_>,
2869    source: &str,
2870    mut visit: impl FnMut(&[String], bool) -> bool,
2871) -> bool {
2872    let mut stopped = false;
2873    walk_named_tree_preorder(root, true, |node| {
2874        if node.kind() == "comment" {
2875            return WalkControl::SkipChildren;
2876        }
2877        if node.kind() == "preproc_arg" {
2878            stopped = object_macro_replacement_type_references(node, source)
2879                .into_iter()
2880                .any(|reference| visit(&reference.components, reference.global));
2881            return if stopped {
2882                WalkControl::Break
2883            } else {
2884                WalkControl::SkipChildren
2885            };
2886        }
2887        if node.kind() == "field_declaration"
2888            && let Some(return_type) = recovered_macro_return_type_node(node, source)
2889        {
2890            let components = [node_text(return_type, source).to_string()];
2891            stopped = visit(&components, false);
2892            if stopped {
2893                return WalkControl::Break;
2894            }
2895        }
2896        if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2897            for candidate in [node, type_node] {
2898                let Some((components, global)) = type_reference_components(candidate, source)
2899                else {
2900                    continue;
2901                };
2902                stopped = visit(&components, global);
2903                if stopped {
2904                    return WalkControl::Break;
2905                }
2906            }
2907        }
2908        let Some((components, global)) = type_reference_components(node, source) else {
2909            return WalkControl::Continue;
2910        };
2911        if node.parent().is_some_and(|parent| {
2912            (matches!(parent.kind(), "template_type" | "template_function")
2913                && parent.child_by_field_name("name") == Some(node))
2914                || (matches!(
2915                    parent.kind(),
2916                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2917                ) && (parent.child_by_field_name("scope") == Some(node)
2918                    || parent.child_by_field_name("name") == Some(node)))
2919        }) {
2920            return WalkControl::Continue;
2921        }
2922        stopped = visit(&components, global);
2923        if stopped {
2924            WalkControl::Break
2925        } else {
2926            WalkControl::Continue
2927        }
2928    });
2929    stopped
2930}
2931
2932/// Whether this file decides the reference against the scan target.
2933///
2934/// The last resort of type admission has no structured resolution left: it
2935/// only knows that the spelling mentions the target's name and that the target
2936/// itself is not visible here. That is undecidable when nothing else declares
2937/// the name, but it is a proven negative when the file already makes another
2938/// declaration of the same name visible, because unqualified lookup binds the
2939/// reference to that declaration and the include closure never reaches the
2940/// target. `resolve_type_node_lexically_for_target` asks the visibility index
2941/// the same question before it resolves; asking it again here keeps the
2942/// inverse from admitting a site that forward resolution has already decided
2943/// (#2916).
2944fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2945    let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2946        return false;
2947    };
2948    let terminal = components
2949        .last()
2950        .expect("type reference components are non-empty");
2951    if terminal != ctx.spec.target.identifier() {
2952        return false;
2953    }
2954    // A concrete specialization is selected from the template arguments, not
2955    // from the visible primary name, so the name-level candidate set cannot
2956    // decide it. `resolve_type_node_lexically_for_target` makes the same
2957    // exception before its own structured prefilter.
2958    if cpp_template_reference_arguments(node, ctx.source).is_some()
2959        && ctx.visibility.is_template_specialization(&ctx.spec.target)
2960    {
2961        return false;
2962    }
2963    // One C++ type reached through a forward declaration in one header and its
2964    // definition in another is two CodeUnits with different signatures and one
2965    // qualified name. A file that sees either of them sees the target's type,
2966    // so only a candidate under a different qualified name decides the
2967    // reference against it. This is what separates the decoy in an unrelated
2968    // namespace from a redeclaration of the target itself.
2969    if ctx
2970        .visibility
2971        .visible_identifier_candidates(ctx.file, terminal)
2972        .any(|candidate| {
2973            candidate.kind() == ctx.spec.target.kind()
2974                && candidate.fq_name() == ctx.spec.target.fq_name()
2975        })
2976    {
2977        return false;
2978    }
2979    !ctx.visibility
2980        .structured_type_reference_may_resolve_to_target(
2981            &ctx.analyzer,
2982            ctx.file,
2983            std::slice::from_ref(terminal),
2984            false,
2985            &[],
2986            &ctx.spec.target,
2987        )
2988}
2989
2990fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2991    let Some(scope) = node.child_by_field_name("scope") else {
2992        return false;
2993    };
2994    let mut pending = vec![scope];
2995    while let Some(candidate) = pending.pop() {
2996        if candidate.kind() == "template_type" {
2997            return true;
2998        }
2999        if matches!(
3000            candidate.kind(),
3001            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3002        ) {
3003            if let Some(scope) = candidate.child_by_field_name("scope") {
3004                pending.push(scope);
3005            }
3006            if let Some(name) = candidate.child_by_field_name("name") {
3007                pending.push(name);
3008            }
3009        }
3010    }
3011    false
3012}
3013
3014/// Whether an undecided reference to a conditional alias family keeps the
3015/// target as one of its alternatives. Guards that select no arm leave every
3016/// compatible arm, and the type it names, a candidate rather than proof
3017/// (#6902).
3018fn guarded_alias_alternatives_include_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
3019    let Some((components, global)) = type_reference_components(node, ctx.source) else {
3020        return false;
3021    };
3022    // The arms are candidates whose guards need not hold here, so look them up
3023    // without the in-context visibility filter that would drop them.
3024    let lexical_scope = if global {
3025        Vec::new()
3026    } else {
3027        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
3028            node,
3029            &ctx.analyzer,
3030            ctx.visibility,
3031            ctx.file,
3032            ctx.source,
3033            true,
3034            false,
3035            Some(&ctx.lexical_scope_cache),
3036        ) {
3037            LexicalScopeResolution::Resolved(scope) => scope,
3038            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return false,
3039        }
3040    };
3041    let LexicalTypeResolution::Resolved { candidates, .. } = ctx
3042        .visibility
3043        .resolve_type_components_lexically_for_forward(
3044            &ctx.analyzer,
3045            ctx.file,
3046            &components,
3047            global,
3048            &lexical_scope,
3049        )
3050    else {
3051        return false;
3052    };
3053    let Some(GuardedAliasSelection::Alternatives(alternatives)) = ctx
3054        .visibility
3055        .guarded_alias_selection_at_reference(&ctx.analyzer, ctx.file, node, &candidates)
3056    else {
3057        return false;
3058    };
3059    alternatives
3060        .iter()
3061        .chain(&candidates)
3062        .any(|unit| same_visible_symbol(unit, &ctx.spec.target))
3063}
3064
3065fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
3066    ctx.target_group.iter().find(|target| {
3067        same_logical_symbol(target, &ctx.spec.target)
3068            && ctx.visibility.is_physically_visible(ctx.file, target)
3069    })
3070}
3071
3072/// Preserve the terminal token of a resolver-proven alias construction.
3073/// Ordinary qualified types and template aliases retain their full type range.
3074/// Alias identity and the construction context are proven by the resolver.
3075fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
3076    if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
3077        push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
3078    } else {
3079        push_type_hit(type_reference_hit_node(node), ctx);
3080    }
3081}
3082
3083fn resolved_alias_terminal_node<'tree>(
3084    node: Node<'tree>,
3085    candidates: &[CodeUnit],
3086    ctx: &ScanCtx<'_>,
3087) -> Option<Node<'tree>> {
3088    let [candidate] = candidates else {
3089        return None;
3090    };
3091    let alias_provider = ctx.analyzer.type_alias_provider()?;
3092    let candidate_is_alias = alias_provider.is_type_alias(candidate)
3093        && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
3094    let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
3095        && same_visible_symbol(candidate, &ctx.spec.target);
3096    if !(candidate_is_alias || target_is_alias) {
3097        return None;
3098    }
3099    if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
3100        return None;
3101    }
3102    let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
3103        node,
3104        &ctx.spec.target,
3105        &ctx.analyzer,
3106        ctx.visibility,
3107        ctx.file,
3108        ctx.source,
3109    );
3110    if reference
3111        != Some(
3112            crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
3113        )
3114    {
3115        return None;
3116    }
3117    cpp_name_component_nodes(node)?.last().copied()
3118}
3119
3120fn target_guided_missing_direct_temporary_type<'tree>(
3121    function: Node<'tree>,
3122    ctx: &ScanCtx<'_>,
3123) -> Option<Node<'tree>> {
3124    let target = physically_visible_type_target(ctx)?;
3125    let component_nodes = cpp_name_component_nodes(function)?;
3126    let terminal = component_nodes.last().copied()?;
3127    if node_text(terminal, ctx.source) != target.identifier() {
3128        return None;
3129    }
3130    let components = component_nodes
3131        .iter()
3132        .map(|component| node_text(*component, ctx.source).to_string())
3133        .collect::<Vec<_>>();
3134    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
3135    indexed_scope_matches_target_name(
3136        &indexed_scope,
3137        &components,
3138        is_globally_qualified_cpp_name(function),
3139        target,
3140    )
3141    .then_some(terminal)
3142}
3143
3144fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
3145    let Some(function) = call.child_by_field_name("function") else {
3146        return;
3147    };
3148    if !matches!(
3149        function.kind(),
3150        "identifier"
3151            | "type_identifier"
3152            | "template_function"
3153            | "template_type"
3154            | "qualified_identifier"
3155            | "scoped_identifier"
3156            | "scoped_type_identifier"
3157    ) {
3158        return;
3159    }
3160    if !type_reference_components_may_name_target(function, ctx) {
3161        return;
3162    }
3163    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3164        *ctx.raw_match_count += 1;
3165        for scope in scopes {
3166            push_type_hit(scope, ctx);
3167        }
3168        return;
3169    }
3170    let terminal = function_terminal_node(function);
3171    let name = node_text(terminal, ctx.source);
3172    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3173        return;
3174    }
3175    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3176        match resolve_declaring_member_owner(
3177            &ctx.analyzer,
3178            ctx.visibility,
3179            ctx.file,
3180            &enclosing_owner,
3181            name,
3182        ) {
3183            EnclosingMemberOwnerResolution::Owner(owner)
3184                if matches!(
3185                    ctx.visibility
3186                        .visible_member_for_owner_name(ctx.file, &owner, name,),
3187                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3188                ) =>
3189            {
3190                return;
3191            }
3192            EnclosingMemberOwnerResolution::Ambiguous => return,
3193            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3194        }
3195    }
3196
3197    // A type alias used as a direct temporary (`result_type(value)`) is parsed
3198    // as an ordinary identifier call. Callable lookup can be ambiguous when
3199    // parser recovery flattens a namespace or same-spelled aliases are
3200    // visible from sibling distributions. An exact enclosing class owner
3201    // proves the member alias without treating the call as an arbitrary name;
3202    // retain the shadow and declaration-visibility guards above and below.
3203    if ctx
3204        .analyzer
3205        .type_alias_provider()
3206        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3207        && name == ctx.spec.target.identifier()
3208        && physically_visible_type_target(ctx).is_some()
3209        && !local_type_name_shadows(function, ctx)
3210        && type_alias_owner_matches_structured_reference(function, ctx)
3211        && ctx.visibility.external_type_candidate_visible_in_context(
3212            &ctx.analyzer,
3213            ctx.file,
3214            &ctx.spec.target,
3215            function,
3216        )
3217    {
3218        *ctx.raw_match_count += 1;
3219        push_type_hit(terminal, ctx);
3220        return;
3221    }
3222
3223    let call_resolution = resolve_qualified_call_target(
3224        call,
3225        function,
3226        &ctx.analyzer,
3227        ctx.visibility,
3228        &ctx.ordinary_type_imports,
3229        ctx.file,
3230        ctx.source,
3231    );
3232    match call_resolution {
3233        BareCallTargetResolution::Type(unit) => {
3234            if same_visible_symbol(&unit, &ctx.spec.target) {
3235                *ctx.raw_match_count += 1;
3236                push_type_hit(function, ctx);
3237                return;
3238            }
3239        }
3240        BareCallTargetResolution::FreeFunctions(units)
3241            if units.iter().all(|unit| {
3242                unit.fq_name() == ctx.spec.target.fq_name()
3243                    && ctx
3244                        .visibility
3245                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
3246            }) => {}
3247        BareCallTargetResolution::Ambiguous => {
3248            push_unproven_hit(function, ctx);
3249            return;
3250        }
3251        BareCallTargetResolution::FreeFunctions(_)
3252        | BareCallTargetResolution::UnprovenFreeFunctions(_)
3253        | BareCallTargetResolution::CallableShadow => return,
3254        // Generated `.c` includes can leave ordinary callable resolution with
3255        // no active callable even when target-preserving type resolution can
3256        // prove the constructor's class. Let the structured type fallback
3257        // below make that decision.
3258        BareCallTargetResolution::Missing => {}
3259    }
3260    let target_resolution = resolve_type_node_lexically_for_target(
3261        function,
3262        &ctx.analyzer,
3263        ctx.visibility,
3264        &ctx.ordinary_type_imports,
3265        ctx.file,
3266        ctx.source,
3267        &ctx.spec.target,
3268        Some(&ctx.lexical_scope_cache),
3269        ctx.recovered_sentinel_scope(function).as_deref(),
3270    );
3271    match target_resolution {
3272        LexicalTypeResolution::Resolved {
3273            unit, candidates, ..
3274        } if same_visible_symbol(&unit, &ctx.spec.target)
3275            || candidates
3276                .iter()
3277                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3278        {
3279            *ctx.raw_match_count += 1;
3280            push_type_hit(function, ctx);
3281        }
3282        LexicalTypeResolution::Missing => {
3283            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3284                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3285            {
3286                *ctx.raw_match_count += 1;
3287                push_type_hit(hit, ctx);
3288            }
3289        }
3290        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3291    }
3292}
3293
3294pub enum BareCallTargetResolution {
3295    Type(CodeUnit),
3296    FreeFunctions(Vec<CodeUnit>),
3297    UnprovenFreeFunctions(Vec<CodeUnit>),
3298    CallableShadow,
3299    Ambiguous,
3300    Missing,
3301}
3302
3303pub enum BlockUsingCallTargetResolution {
3304    Target(BareCallTargetResolution),
3305    Unindexed(Vec<String>),
3306    Ambiguous,
3307}
3308
3309#[allow(clippy::too_many_arguments)]
3310fn resolve_qualified_call_target(
3311    call: Node<'_>,
3312    function: Node<'_>,
3313    analyzer: &CppGraphSource<'_>,
3314    visibility: &VisibilityIndex,
3315    ordinary_type_imports: &OrdinaryTypeImportCell,
3316    file: &ProjectFile,
3317    source: &str,
3318) -> BareCallTargetResolution {
3319    if matches!(function.kind(), "identifier" | "template_function") {
3320        return resolve_bare_call_target(
3321            call,
3322            function,
3323            analyzer,
3324            visibility,
3325            ordinary_type_imports,
3326            file,
3327            source,
3328        );
3329    }
3330    if !matches!(
3331        function.kind(),
3332        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3333    ) {
3334        return BareCallTargetResolution::Missing;
3335    }
3336    let terminal = function_terminal_node(function);
3337    let name = node_text(terminal, source);
3338    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3339        return BareCallTargetResolution::Missing;
3340    };
3341    components.push(name.to_string());
3342    let qualified_name = components.join("::");
3343    let type_resolution = resolve_type_node_lexically(
3344        function,
3345        analyzer,
3346        visibility,
3347        ordinary_type_imports,
3348        file,
3349        source,
3350    );
3351    let same_name_resolves_to_type = matches!(
3352        &type_resolution,
3353        LexicalTypeResolution::Resolved {
3354            unit,
3355            candidates,
3356            ..
3357        } if cpp_name_for(unit) == qualified_name
3358            || candidates
3359                .iter()
3360                .any(|candidate| cpp_name_for(candidate) == qualified_name)
3361    );
3362    let has_explicit_template_arguments =
3363        cpp_template_reference_arguments(function, source).is_some();
3364    let candidates = visibility
3365        .visible_identifier_candidates(file, name)
3366        .filter(|candidate| {
3367            candidate.is_function()
3368                && type_owner_of(analyzer, candidate).is_none()
3369                && !(same_name_resolves_to_type
3370                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3371                        || has_explicit_template_arguments
3372                            && visibility
3373                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
3374                && cpp_name_for(candidate) == qualified_name
3375                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3376        })
3377        .cloned()
3378        .collect::<Vec<_>>();
3379    if !candidates.is_empty() {
3380        return resolve_callable_candidates(
3381            candidates,
3382            visibility.call_arity_evidence(file, call, source).exact(),
3383            call.start_byte(),
3384            analyzer,
3385            visibility,
3386            file,
3387        );
3388    }
3389    match type_resolution {
3390        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3391        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3392        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3393    }
3394}
3395
3396fn ordinary_binding_lookup_targets(
3397    binding: &OrdinaryTypeImport,
3398    active_bindings: &[&OrdinaryTypeImport],
3399    name: &str,
3400) -> Vec<Vec<String>> {
3401    let Some(qualified) = binding.resolved_target_components.as_ref() else {
3402        return Vec::new();
3403    };
3404    let mut targets = Vec::new();
3405    match binding.target {
3406        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3407        EffectiveUsingTarget::Namespace { .. } => {
3408            let mut stack = vec![qualified.clone()];
3409            let mut visited = HashSet::default();
3410            while let Some(namespace) = stack.pop() {
3411                if !visited.insert(namespace.clone()) {
3412                    continue;
3413                }
3414                let mut target = namespace.clone();
3415                target.push(name.to_string());
3416                targets.push(target);
3417                stack.extend(active_bindings.iter().filter_map(|candidate| {
3418                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3419                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3420                    .then(|| candidate.resolved_target_components.clone())
3421                    .flatten()
3422                }));
3423            }
3424        }
3425    }
3426    targets
3427}
3428
3429fn binding_free_function_candidates(
3430    binding: &OrdinaryTypeImport,
3431    active_bindings: &[&OrdinaryTypeImport],
3432    analyzer: &CppGraphSource<'_>,
3433    visibility: &VisibilityIndex<'_>,
3434    file: &ProjectFile,
3435    name: &str,
3436    reference_byte: usize,
3437) -> Vec<CodeUnit> {
3438    ordinary_binding_lookup_targets(binding, active_bindings, name)
3439        .into_iter()
3440        .flat_map(|target| {
3441            let qualified_name = target.join("::");
3442            visibility
3443                .visible_identifier_candidates(file, name)
3444                .filter(move |candidate| {
3445                    candidate.is_function()
3446                        && type_owner_of(analyzer, candidate).is_none()
3447                        && cpp_name_for(candidate) == qualified_name
3448                        && visibility.declaration_visible_at(
3449                            analyzer,
3450                            file,
3451                            candidate,
3452                            reference_byte,
3453                        )
3454                })
3455                .cloned()
3456        })
3457        .collect()
3458}
3459
3460/// Collapse the candidates unqualified lookup found to one entry per logical
3461/// callable, keeping the first spelling of each.
3462///
3463/// A header declaration and its out-of-line body spell one parameter type
3464/// differently often enough that the persisted signature strings disagree, so
3465/// the string triple alone reports one C++ declaration as an overload set and
3466/// an unproven argument count then turns it into ambiguity.
3467/// `same_logical_callable` answers the string question first and resolves the
3468/// written parameter names only when the strings differ (#2010).
3469fn dedupe_callable_candidates(
3470    candidates: &mut Vec<CodeUnit>,
3471    analyzer: &CppGraphSource<'_>,
3472    visibility: &VisibilityIndex<'_>,
3473) {
3474    let mut deduped = Vec::with_capacity(candidates.len());
3475    for candidate in candidates.drain(..) {
3476        if !deduped
3477            .iter()
3478            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3479        {
3480            deduped.push(candidate);
3481        }
3482    }
3483    *candidates = deduped;
3484}
3485
3486fn resolve_callable_candidates(
3487    candidates: Vec<CodeUnit>,
3488    call_arity: Option<usize>,
3489    reference_byte: usize,
3490    analyzer: &CppGraphSource<'_>,
3491    visibility: &VisibilityIndex<'_>,
3492    file: &ProjectFile,
3493) -> BareCallTargetResolution {
3494    let mut candidates = candidates;
3495    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3496    if candidates.is_empty() {
3497        return BareCallTargetResolution::Missing;
3498    }
3499    let Some(call_arity) = call_arity else {
3500        // An unproven argument count cannot create ambiguity where lookup found
3501        // exactly one name binding: there is nothing to be ambiguous between.
3502        // C has no overloading at all, and a lone C++ candidate is the only
3503        // declaration unqualified lookup reached, so arity cannot pick another
3504        // one (#1811). Keeping it unproven discarded the proven candidate and
3505        // answered `ambiguous` with an empty definition list.
3506        if candidates.len() == 1 {
3507            return BareCallTargetResolution::FreeFunctions(candidates);
3508        }
3509        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3510    };
3511    let applicable = candidates
3512        .into_iter()
3513        .filter(|candidate| {
3514            visibility
3515                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3516                .is_some_and(|arity| arity.accepts(call_arity))
3517        })
3518        .collect::<Vec<_>>();
3519    if applicable.is_empty() {
3520        BareCallTargetResolution::CallableShadow
3521    } else {
3522        BareCallTargetResolution::FreeFunctions(applicable)
3523    }
3524}
3525
3526fn deduplicate_type_import_candidates(
3527    candidates: Vec<(CodeUnit, Vec<String>)>,
3528) -> Vec<(CodeUnit, Vec<String>)> {
3529    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3530    for candidate in candidates {
3531        if !logical
3532            .iter()
3533            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3534        {
3535            logical.push(candidate);
3536        }
3537    }
3538    logical
3539}
3540
3541fn resolve_direct_type_candidates(
3542    candidates: Vec<(CodeUnit, Vec<String>)>,
3543    analyzer: &CppGraphSource<'_>,
3544    visibility: &VisibilityIndex<'_>,
3545    file: &ProjectFile,
3546) -> BareCallTargetResolution {
3547    let logical = deduplicate_type_import_candidates(candidates);
3548    let [(target, components)] = logical.as_slice() else {
3549        return if logical.is_empty() {
3550            BareCallTargetResolution::Missing
3551        } else {
3552            BareCallTargetResolution::Ambiguous
3553        };
3554    };
3555    match visibility
3556        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3557    {
3558        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3559        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3560        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3561    }
3562}
3563
3564/// Resolve a direct using-declaration in the nearest concrete block before
3565/// class-member lookup. A block declaration such as `using std::swap;` adds
3566/// that name to the block scope and hides a same-named member. If the imported
3567/// target is not indexed, retain its structured path as boundary evidence.
3568#[allow(clippy::too_many_arguments)]
3569pub fn resolve_block_using_call_target(
3570    call: Node<'_>,
3571    function: Node<'_>,
3572    analyzer: &CppGraphSource<'_>,
3573    visibility: &VisibilityIndex<'_>,
3574    ordinary_type_imports: &OrdinaryTypeImportCell,
3575    file: &ProjectFile,
3576    source: &str,
3577) -> Option<BlockUsingCallTargetResolution> {
3578    if !matches!(function.kind(), "identifier" | "template_function") {
3579        return None;
3580    }
3581    let name = node_text(function_terminal_node(function), source);
3582    if name.is_empty() {
3583        return None;
3584    }
3585    let bindings = effective_using_bindings_for_name(
3586        visibility,
3587        ordinary_type_imports,
3588        file,
3589        function,
3590        source,
3591        name,
3592    );
3593    let block_bindings = bindings
3594        .iter()
3595        .filter(|binding| {
3596            binding.namespace_scope.is_none()
3597                && binding.block_scope
3598                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3599        })
3600        .collect::<Vec<_>>();
3601    if block_bindings.is_empty() {
3602        return None;
3603    }
3604    let lexical_scope =
3605        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3606            LexicalScopeResolution::Resolved(scope) => scope,
3607            LexicalScopeResolution::Ambiguous => {
3608                return Some(BlockUsingCallTargetResolution::Ambiguous);
3609            }
3610            LexicalScopeResolution::Missing => return None,
3611        };
3612    let reference_guards = preprocessor_guard_environment(function, source);
3613    let active = block_bindings
3614        .into_iter()
3615        .filter(|binding| {
3616            effective_using_binding_active(
3617                binding,
3618                function,
3619                &lexical_scope,
3620                reference_guards.as_ref(),
3621                visibility,
3622                file,
3623            )
3624        })
3625        .collect::<Vec<_>>();
3626    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3627    let at_tier = active
3628        .into_iter()
3629        .filter(|binding| binding.scope_depth == depth)
3630        .collect::<Vec<_>>();
3631    let callable_candidates = at_tier
3632        .iter()
3633        .flat_map(|binding| {
3634            binding_free_function_candidates(
3635                binding,
3636                &[],
3637                analyzer,
3638                visibility,
3639                file,
3640                name,
3641                call.start_byte(),
3642            )
3643        })
3644        .collect::<Vec<_>>();
3645    if !callable_candidates.is_empty() {
3646        return Some(BlockUsingCallTargetResolution::Target(
3647            resolve_callable_candidates(
3648                callable_candidates,
3649                visibility.call_arity_evidence(file, call, source).exact(),
3650                call.start_byte(),
3651                analyzer,
3652                visibility,
3653                file,
3654            ),
3655        ));
3656    }
3657    let type_candidates = at_tier
3658        .iter()
3659        .flat_map(|binding| {
3660            binding_type_candidates(
3661                binding,
3662                &[],
3663                analyzer,
3664                visibility,
3665                file,
3666                name,
3667                None,
3668                call.start_byte(),
3669            )
3670        })
3671        .collect::<Vec<_>>();
3672    if !type_candidates.is_empty() {
3673        return Some(BlockUsingCallTargetResolution::Target(
3674            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3675        ));
3676    }
3677
3678    let mut unindexed = Vec::new();
3679    for binding in at_tier {
3680        let Some(components) = binding.resolved_target_components.as_ref() else {
3681            continue;
3682        };
3683        if !unindexed.contains(components) {
3684            unindexed.push(components.clone());
3685        }
3686    }
3687    match unindexed.as_slice() {
3688        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3689        [] => None,
3690        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3691    }
3692}
3693
3694#[allow(clippy::too_many_arguments)]
3695pub fn resolve_bare_call_target(
3696    call: Node<'_>,
3697    function: Node<'_>,
3698    analyzer: &CppGraphSource<'_>,
3699    visibility: &VisibilityIndex<'_>,
3700    ordinary_type_imports: &OrdinaryTypeImportCell,
3701    file: &ProjectFile,
3702    source: &str,
3703) -> BareCallTargetResolution {
3704    if !matches!(function.kind(), "identifier" | "template_function") {
3705        return BareCallTargetResolution::Missing;
3706    }
3707    let terminal = function_terminal_node(function);
3708    let name = node_text(terminal, source);
3709    if name.is_empty() {
3710        return BareCallTargetResolution::Missing;
3711    }
3712    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3713    let lexical_scope =
3714        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3715            LexicalScopeResolution::Resolved(scope) => scope,
3716            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3717            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3718        };
3719    let type_resolution = resolve_type_node_lexically(
3720        function,
3721        analyzer,
3722        visibility,
3723        ordinary_type_imports,
3724        file,
3725        source,
3726    );
3727    let type_components = match &type_resolution {
3728        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3729        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3730    };
3731    let direct_type_resolution = visibility.resolve_type_components_lexically(
3732        analyzer,
3733        file,
3734        &[name.to_string()],
3735        false,
3736        &lexical_scope,
3737    );
3738    let direct_type_components = match &direct_type_resolution {
3739        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3740        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3741    };
3742    let has_explicit_template_arguments =
3743        cpp_template_reference_arguments(function, source).is_some();
3744    let bindings = effective_using_bindings_for_name(
3745        visibility,
3746        ordinary_type_imports,
3747        file,
3748        function,
3749        source,
3750        name,
3751    );
3752    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3753    // step re-descends from the root (#1927): with no bindings both filters
3754    // below select nothing, so the environment is never consulted.
3755    let function_guards = if bindings.is_empty() {
3756        None
3757    } else {
3758        preprocessor_guard_environment(function, source)
3759    };
3760    let active_bindings = bindings
3761        .iter()
3762        .filter(|binding| {
3763            effective_using_binding_active(
3764                binding,
3765                function,
3766                &lexical_scope,
3767                function_guards.as_ref(),
3768                visibility,
3769                file,
3770            )
3771        })
3772        .collect::<Vec<_>>();
3773    let transitive_bindings = bindings
3774        .iter()
3775        .filter(|binding| {
3776            effective_using_binding_guards_active(
3777                binding,
3778                function.start_byte(),
3779                function_guards.as_ref(),
3780                visibility,
3781                file,
3782            ) && (binding.namespace_scope.is_some()
3783                || (binding.scope_start <= function.start_byte()
3784                    && function.end_byte() <= binding.scope_end))
3785        })
3786        .collect::<Vec<_>>();
3787    let mut concrete_depths = active_bindings
3788        .iter()
3789        .filter(|binding| binding.namespace_scope.is_none())
3790        .map(|binding| binding.scope_depth)
3791        .collect::<Vec<_>>();
3792    concrete_depths.sort_unstable();
3793    concrete_depths.dedup();
3794    for depth in concrete_depths.into_iter().rev() {
3795        let at_tier = active_bindings
3796            .iter()
3797            .copied()
3798            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3799        let direct = at_tier
3800            .clone()
3801            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3802            .flat_map(|binding| {
3803                binding_free_function_candidates(
3804                    binding,
3805                    &transitive_bindings,
3806                    analyzer,
3807                    visibility,
3808                    file,
3809                    name,
3810                    call.start_byte(),
3811                )
3812            })
3813            .collect::<Vec<_>>();
3814        if !direct.is_empty() {
3815            return resolve_callable_candidates(
3816                direct,
3817                call_arity,
3818                call.start_byte(),
3819                analyzer,
3820                visibility,
3821                file,
3822            );
3823        }
3824        let direct_types = at_tier
3825            .clone()
3826            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3827            .flat_map(|binding| {
3828                binding_type_candidates(
3829                    binding,
3830                    &transitive_bindings,
3831                    analyzer,
3832                    visibility,
3833                    file,
3834                    name,
3835                    None,
3836                    call.start_byte(),
3837                )
3838            })
3839            .collect::<Vec<_>>();
3840        if !direct_types.is_empty() {
3841            // `resolve_direct_type_candidates` never consults the argument
3842            // count: it answers the one type the name binds to, or reports the
3843            // competing types. An unknown count therefore cannot make this
3844            // ambiguous (#1812).
3845            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3846        }
3847        let directives = at_tier
3848            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3849            .flat_map(|binding| {
3850                binding_free_function_candidates(
3851                    binding,
3852                    &transitive_bindings,
3853                    analyzer,
3854                    visibility,
3855                    file,
3856                    name,
3857                    call.start_byte(),
3858                )
3859            })
3860            .collect::<Vec<_>>();
3861        if !directives.is_empty() {
3862            return resolve_callable_candidates(
3863                directives,
3864                call_arity,
3865                call.start_byte(),
3866                analyzer,
3867                visibility,
3868                file,
3869            );
3870        }
3871    }
3872    for prefix_len in (0..=lexical_scope.len()).rev() {
3873        let mut qualified = lexical_scope[..prefix_len].to_vec();
3874        qualified.push(name.to_string());
3875        let same_name_resolves_to_type = direct_type_components
3876            .is_some_and(|components| components == qualified.as_slice())
3877            || type_components.is_some_and(|components| components == qualified.as_slice());
3878        let mut direct = visibility
3879            .visible_identifier_candidates(file, name)
3880            .filter(|candidate| {
3881                candidate.is_function()
3882                    && type_owner_of(analyzer, candidate).is_none()
3883                    && !(same_name_resolves_to_type
3884                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3885                            || has_explicit_template_arguments
3886                                && visibility
3887                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3888                    && cpp_name_for(candidate) == qualified.join("::")
3889                    && if analyzer.reference_uses_c_semantics(file) {
3890                        visibility.declaration_visible_for_c_forward_call(
3891                            analyzer,
3892                            file,
3893                            candidate,
3894                            call.start_byte(),
3895                        )
3896                    } else {
3897                        visibility.declaration_visible_at(
3898                            analyzer,
3899                            file,
3900                            candidate,
3901                            call.start_byte(),
3902                        )
3903                    }
3904            })
3905            .cloned()
3906            .collect::<Vec<_>>();
3907        let at_tier = active_bindings.iter().copied().filter(|binding| {
3908            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3909        });
3910        direct.extend(
3911            at_tier
3912                .clone()
3913                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3914                .flat_map(|binding| {
3915                    binding_free_function_candidates(
3916                        binding,
3917                        &transitive_bindings,
3918                        analyzer,
3919                        visibility,
3920                        file,
3921                        name,
3922                        call.start_byte(),
3923                    )
3924                }),
3925        );
3926        if !direct.is_empty() {
3927            return resolve_callable_candidates(
3928                direct,
3929                call_arity,
3930                call.start_byte(),
3931                analyzer,
3932                visibility,
3933                file,
3934            );
3935        }
3936        let mut direct_types = at_tier
3937            .clone()
3938            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3939            .flat_map(|binding| {
3940                binding_type_candidates(
3941                    binding,
3942                    &transitive_bindings,
3943                    analyzer,
3944                    visibility,
3945                    file,
3946                    name,
3947                    None,
3948                    call.start_byte(),
3949                )
3950            })
3951            .collect::<Vec<_>>();
3952        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3953            && let LexicalTypeResolution::Resolved {
3954                unit, components, ..
3955            } = &direct_type_resolution
3956        {
3957            direct_types.push((unit.clone(), components.clone()));
3958        }
3959        if !direct_types.is_empty() {
3960            // `resolve_direct_type_candidates` never consults the argument
3961            // count: it answers the one type the name binds to, or reports the
3962            // competing types. An unknown count therefore cannot make this
3963            // ambiguous (#1812).
3964            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3965        }
3966        let directives = at_tier
3967            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3968            .flat_map(|binding| {
3969                binding_free_function_candidates(
3970                    binding,
3971                    &transitive_bindings,
3972                    analyzer,
3973                    visibility,
3974                    file,
3975                    name,
3976                    call.start_byte(),
3977                )
3978            })
3979            .collect::<Vec<_>>();
3980        if !directives.is_empty() {
3981            return resolve_callable_candidates(
3982                directives,
3983                call_arity,
3984                call.start_byte(),
3985                analyzer,
3986                visibility,
3987                file,
3988            );
3989        }
3990        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3991            // The lexical type resolution below already answers with the single
3992            // type, or with its own ambiguity verdict; the argument count adds
3993            // nothing to that decision (#1812).
3994            return match type_resolution {
3995                LexicalTypeResolution::Resolved { unit, .. } => {
3996                    BareCallTargetResolution::Type(unit)
3997                }
3998                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3999                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
4000            };
4001        }
4002    }
4003    // Every lookup tier is exhausted: no callable and no type candidate was
4004    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
4005    // at all, and its early return in get_definition preempted the same-file
4006    // macro fallback - so a call to a macro defined in the referencing file
4007    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
4008    // resolve once an unresolvable include made the argument count unknown.
4009    // A no-candidate outcome is Missing, which is what makes the fallback
4010    // reachable (#1812).
4011    match type_resolution {
4012        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
4013        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
4014        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
4015    }
4016}
4017
4018fn static_qualifier_type_scopes<'tree>(
4019    node: Node<'tree>,
4020    ctx: &ScanCtx<'_>,
4021) -> Option<Vec<Node<'tree>>> {
4022    if !matches!(
4023        node.kind(),
4024        "qualified_identifier" | "scoped_type_identifier"
4025    ) {
4026        return None;
4027    }
4028    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
4029    // this root contains every structured component needed for prefix lookup.
4030    debug_assert!(!is_nested_type_node(node));
4031    let qualified = qualified_owner_components(node, ctx.source)?;
4032    static_qualifier_type_scopes_for_components(node, qualified, ctx)
4033}
4034
4035fn static_qualifier_type_scopes_for_components<'tree>(
4036    node: Node<'tree>,
4037    qualified: QualifiedOwnerComponents<'tree>,
4038    ctx: &ScanCtx<'_>,
4039) -> Option<Vec<Node<'tree>>> {
4040    if !qualified.global
4041        && let Some(name) = qualified.names.first()
4042        && let Some(alias_type) = local_receiver_alias_type_node(node, name, ctx)
4043    {
4044        return match resolve_receiver_type_node(alias_type, ctx) {
4045            Ok(Some(owner)) if same_visible_symbol(&owner, &ctx.spec.target) => {
4046                Some(vec![qualified.nodes[0]])
4047            }
4048            _ => None,
4049        };
4050    }
4051    let mut matches = Vec::new();
4052    let mut inherited_injected_name_is_shadowed = false;
4053    for component_count in 1..=qualified.names.len() {
4054        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
4055            node,
4056            &qualified.names[..component_count],
4057            qualified.global,
4058            &ctx.analyzer,
4059            ctx.visibility,
4060            &ctx.ordinary_type_imports,
4061            ctx.file,
4062            ctx.source,
4063            &ctx.spec.target,
4064            false,
4065            Some(&ctx.lexical_scope_cache),
4066        );
4067        match resolution {
4068            LexicalTypeResolution::Resolved {
4069                unit, candidates, ..
4070            } if (!ctx
4071                .analyzer
4072                .type_alias_provider()
4073                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4074                || ctx.visibility.external_type_candidate_visible_in_context(
4075                    &ctx.analyzer,
4076                    ctx.file,
4077                    &ctx.spec.target,
4078                    node,
4079                ))
4080                && (same_visible_symbol(&unit, &ctx.spec.target)
4081                    || candidates
4082                        .iter()
4083                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
4084                && target_alias_candidates_visible(&candidates, node, ctx) =>
4085            {
4086                let matched =
4087                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
4088                if !template_type_component_preserves_target(matched, &candidates, ctx) {
4089                    continue;
4090                }
4091                if !matches.iter().any(|existing: &Node<'_>| {
4092                    existing.start_byte() == matched.start_byte()
4093                        && existing.end_byte() == matched.end_byte()
4094                }) {
4095                    matches.push(matched);
4096                }
4097            }
4098            // The ordinary lexical resolver can remain ambiguous when the
4099            // qualified terminal is an alias whose canonical target is not
4100            // indexed (for example, `Hash::Digest` aliases an external
4101            // `std::array`). The target-guided path below still requires one
4102            // physically visible logical class for every emitted prefix.
4103            LexicalTypeResolution::Ambiguous => {
4104                return (!inherited_injected_name_is_shadowed)
4105                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4106                    .flatten()
4107                    .map(|scope| vec![scope])
4108                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
4109            }
4110            LexicalTypeResolution::Resolved { .. } => {
4111                if let Some(matched) =
4112                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4113                {
4114                    matches.push(matched);
4115                }
4116                inherited_injected_name_is_shadowed |= component_count == 1;
4117            }
4118            LexicalTypeResolution::Missing => {
4119                if let Some(matched) =
4120                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
4121                {
4122                    matches.push(matched);
4123                }
4124            }
4125        }
4126    }
4127    if matches.is_empty() {
4128        (!inherited_injected_name_is_shadowed)
4129            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4130            .flatten()
4131            .map(|scope| vec![scope])
4132            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
4133    } else {
4134        Some(matches)
4135    }
4136}
4137
4138/// Recover a class owner in a qualified expression when guard-aware lookup
4139/// cannot prove the owner. Keep the hit on the owner component, not the member.
4140enum TargetGuidedQualifiedValueOwnerHit<'tree> {
4141    Proven(Node<'tree>),
4142    Unproven(Node<'tree>),
4143}
4144
4145fn target_guided_qualified_value_owner_hit<'tree>(
4146    node: Node<'tree>,
4147    ctx: &ScanCtx<'_>,
4148) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
4149    let target = physically_visible_type_target(ctx)?;
4150    if !target.is_class() {
4151        return None;
4152    }
4153    let qualified = qualified_owner_components(node, ctx.source)?;
4154    let lexical_scope = match enclosing_lexical_scope_components(
4155        node,
4156        &ctx.analyzer,
4157        ctx.visibility,
4158        ctx.file,
4159        ctx.source,
4160    ) {
4161        LexicalScopeResolution::Resolved(scope) => scope,
4162        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4163            enclosing_namespace_components(node, ctx.source)
4164        }
4165    };
4166    let LexicalTypeResolution::Resolved {
4167        unit, candidates, ..
4168    } = ctx.visibility.resolve_type_components_lexically_for_target(
4169        &ctx.analyzer,
4170        ctx.file,
4171        &qualified.names,
4172        qualified.global,
4173        &lexical_scope,
4174        target,
4175    )
4176    else {
4177        return None;
4178    };
4179    let scope = qualified.nodes.last().copied()?;
4180    // Lexical component lookup does not consider source order, so it can
4181    // select a declaration that follows the reference. The forward selection
4182    // proves the owner only when that declaration is visible at the reference.
4183    let forward_uniquely_selects_target = match ctx
4184        .visibility
4185        .resolve_type_components_lexically_for_forward(
4186            &ctx.analyzer,
4187            ctx.file,
4188            &qualified.names,
4189            qualified.global,
4190            &lexical_scope,
4191        ) {
4192        LexicalTypeResolution::Resolved {
4193            unit, candidates, ..
4194        } if candidates.len() == 1 => {
4195            same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4196                && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4197                && ctx.visibility.external_type_candidate_visible_in_context(
4198                    &ctx.analyzer,
4199                    ctx.file,
4200                    &candidates[0],
4201                    scope,
4202                )
4203        }
4204        LexicalTypeResolution::Resolved { .. }
4205        | LexicalTypeResolution::Ambiguous
4206        | LexicalTypeResolution::Missing => false,
4207    };
4208    if forward_uniquely_selects_target
4209        || (same_visible_symbol(&unit, target)
4210            && !candidates.is_empty()
4211            && candidates
4212                .iter()
4213                .all(|candidate| same_visible_symbol(candidate, target)))
4214    {
4215        Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4216    } else if candidates
4217        .iter()
4218        .any(|candidate| same_visible_symbol(candidate, target))
4219    {
4220        Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4221    } else {
4222        None
4223    }
4224}
4225
4226/// Resolve a nested class-owned alias when the indexed alias path is not a
4227/// standalone type candidate. The C++ index stores `basic_json::type_error`
4228/// as a synthetic child of `basic_json`, while source can qualify it through
4229/// a class alias such as `json::type_error`. Resolve the owner prefix first,
4230/// then canonicalize the structured member alias against the requested type.
4231fn target_guided_nested_alias_type_scope<'tree>(
4232    node: Node<'tree>,
4233    qualified: &QualifiedOwnerComponents<'tree>,
4234    component_count: usize,
4235    ctx: &ScanCtx<'_>,
4236) -> Option<Node<'tree>> {
4237    if component_count < 2 {
4238        return None;
4239    }
4240    let (owner_components, member_name) =
4241        qualified.names[..component_count].split_at(component_count - 1);
4242    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4243        node,
4244        owner_components,
4245        qualified.global,
4246        &ctx.analyzer,
4247        ctx.visibility,
4248        &ctx.ordinary_type_imports,
4249        ctx.file,
4250        ctx.source,
4251    ) else {
4252        return None;
4253    };
4254    let member_name = member_name.first()?;
4255    let alias_provider = ctx.analyzer.type_alias_provider()?;
4256    ctx.visibility
4257        .visible_members_for_owner_name(ctx.file, &owner, member_name)
4258        .into_iter()
4259        .filter(|member| alias_provider.is_type_alias(member))
4260        .find(|member| {
4261            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4262                &ctx.analyzer,
4263                ctx.file,
4264                member,
4265                node,
4266            ) || ctx
4267                .visibility
4268                .external_type_candidate_guard_compatible_in_context(
4269                    &ctx.analyzer,
4270                    ctx.file,
4271                    member,
4272                    node,
4273                );
4274            if !member_visible {
4275                return false;
4276            }
4277            same_visible_symbol(member, &ctx.spec.target)
4278                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4279        })
4280        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4281}
4282
4283fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4284    if ctx.visibility.structured_class_alias_resolves_to_target(
4285        &ctx.analyzer,
4286        ctx.file,
4287        candidate,
4288        &ctx.spec.target,
4289    ) {
4290        return ctx.spec.target.clone();
4291    }
4292    let structured = ctx
4293        .visibility
4294        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4295    structured.unwrap_or_else(|| candidate.clone())
4296}
4297
4298/// Preserve the alias component of a qualified reference when the alias target
4299/// is a dependent nested type and forward lookup retains its primary template
4300/// as the bounded identity.
4301fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4302    ctx.visibility
4303        .external_type_candidate_guard_compatible_in_context(
4304            &ctx.analyzer,
4305            ctx.file,
4306            candidate,
4307            node,
4308        )
4309}
4310
4311fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4312    ctx.visibility.is_physically_visible(ctx.file, candidate)
4313        && class_alias_guard_compatible(candidate, node, ctx)
4314}
4315
4316fn class_alias_path_preserves_target(
4317    candidate: &CodeUnit,
4318    target: &CodeUnit,
4319    ctx: &ScanCtx<'_>,
4320) -> bool {
4321    ctx.visibility.structured_class_alias_path_preserves_target(
4322        &ctx.analyzer,
4323        ctx.file,
4324        candidate,
4325        target,
4326    )
4327}
4328
4329fn target_guided_dependent_alias_qualifier_scope<'tree>(
4330    node: Node<'tree>,
4331    ctx: &ScanCtx<'_>,
4332) -> Option<Node<'tree>> {
4333    if !matches!(
4334        node.kind(),
4335        "qualified_identifier" | "scoped_type_identifier"
4336    ) {
4337        return None;
4338    }
4339    let target = physically_visible_type_target(ctx)?;
4340    let alias_provider = ctx.analyzer.type_alias_provider()?;
4341    if !target.is_class() || alias_provider.is_type_alias(target) {
4342        return None;
4343    }
4344    let nodes = cpp_name_component_nodes(node)?;
4345    let names = nodes
4346        .iter()
4347        .map(|component| node_text(*component, ctx.source).to_string())
4348        .collect::<Vec<_>>();
4349    let global = is_globally_qualified_cpp_name(node);
4350    let lexical_scope = match enclosing_lexical_scope_components(
4351        node,
4352        &ctx.analyzer,
4353        ctx.visibility,
4354        ctx.file,
4355        ctx.source,
4356    ) {
4357        LexicalScopeResolution::Resolved(scope) => scope,
4358        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4359            enclosing_namespace_components(node, ctx.source)
4360        }
4361    };
4362    for component_count in 1..=names.len() {
4363        let components = &names[..component_count];
4364        let name = components.last()?;
4365        let candidates = ctx
4366            .visibility
4367            .visible_identifier_candidates(ctx.file, name)
4368            .filter(|candidate| alias_provider.is_type_alias(candidate))
4369            .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4370            .filter(|candidate| {
4371                let candidate_components = canonical_cpp_scope_components(candidate);
4372                lexical_component_tiers(components, global, &lexical_scope)
4373                    .any(|tier| tier == candidate_components)
4374                    || (component_count == 1
4375                        && member_alias_owner_matches_reference_for(
4376                            candidate,
4377                            nodes[component_count - 1],
4378                            ctx,
4379                        ))
4380            })
4381            .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4382        let mut aliases: Vec<&CodeUnit> = Vec::new();
4383        for candidate in candidates {
4384            if !aliases
4385                .iter()
4386                .any(|existing| same_logical_symbol(existing, candidate))
4387            {
4388                aliases.push(candidate);
4389            }
4390        }
4391        if aliases.len() == 1 {
4392            return nodes.get(component_count - 1).copied();
4393        }
4394        if aliases.len() > 1 {
4395            return None;
4396        }
4397    }
4398    None
4399}
4400
4401/// Preserve an unqualified class-owned alias when its dependent target path
4402/// retains the requested primary template as the bounded forward identity.
4403fn target_guided_dependent_class_alias_leaf<'tree>(
4404    node: Node<'tree>,
4405    ctx: &ScanCtx<'_>,
4406) -> Option<Node<'tree>> {
4407    if node.kind() != "type_identifier"
4408        || is_declaration_name(node)
4409        || local_type_name_shadows(node, ctx)
4410    {
4411        return None;
4412    }
4413    let target = physically_visible_type_target(ctx)?;
4414    let alias_provider = ctx.analyzer.type_alias_provider()?;
4415    if !target.is_class() || alias_provider.is_type_alias(target) {
4416        return None;
4417    }
4418    let name = node_text(node, ctx.source);
4419    let aliases = ctx
4420        .visibility
4421        .visible_identifier_candidates(ctx.file, name)
4422        .filter(|candidate| alias_provider.is_type_alias(candidate))
4423        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4424        .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4425        .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4426        .collect::<Vec<_>>();
4427    matches!(aliases.as_slice(), [_]).then_some(node)
4428}
4429
4430/// Recover a namespace alias whose guard state blocks ordinary visibility.
4431/// Require one visible canonical target and an exact structured alias path.
4432fn target_guided_unproven_alias_type_reference<'tree>(
4433    node: Node<'tree>,
4434    candidates: &[CodeUnit],
4435    ctx: &ScanCtx<'_>,
4436) -> Option<Node<'tree>> {
4437    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4438    let target = physically_visible_type_target(ctx)?;
4439    if !target.is_class() {
4440        return None;
4441    }
4442    let alias_provider = ctx.analyzer.type_alias_provider()?;
4443    let (components, _) = type_reference_components(node, ctx.source)?;
4444    let hit = template_arguments
4445        .as_ref()
4446        .and_then(|_| template_reference_name_node(node))
4447        .map(function_terminal_node)
4448        .unwrap_or_else(|| function_terminal_node(node));
4449    candidates
4450        .iter()
4451        .filter(|candidate| {
4452            alias_provider.is_type_alias(candidate)
4453                && ctx.visibility.is_physically_visible(ctx.file, candidate)
4454                && canonical_cpp_scope_components(candidate) == components
4455        })
4456        .find(|candidate| {
4457            template_arguments.as_ref().map_or_else(
4458                || {
4459                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4460                        || ctx.visibility.structured_alias_primary_preserves_target(
4461                            &ctx.analyzer,
4462                            ctx.file,
4463                            candidate,
4464                            target,
4465                        )
4466                },
4467                |arguments| {
4468                    ctx.visibility.template_alias_arguments_preserve_target(
4469                        &ctx.analyzer,
4470                        ctx.file,
4471                        candidate,
4472                        arguments,
4473                        target,
4474                    )
4475                },
4476            )
4477        })
4478        .map(|_| hit)
4479}
4480
4481fn target_alias_candidates_visible(
4482    candidates: &[CodeUnit],
4483    reference: Node<'_>,
4484    ctx: &ScanCtx<'_>,
4485) -> bool {
4486    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4487        return true;
4488    };
4489    if candidates.iter().any(|candidate| {
4490        !alias_provider.is_type_alias(candidate)
4491            && ctx.visibility.same_template_member_identity(
4492                &ctx.analyzer,
4493                candidate,
4494                &ctx.spec.target,
4495            )
4496    }) {
4497        return true;
4498    }
4499    let target_aliases = candidates
4500        .iter()
4501        .filter(|candidate| {
4502            alias_provider.is_type_alias(candidate)
4503                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4504        })
4505        .collect::<Vec<_>>();
4506    target_aliases.is_empty()
4507        || target_aliases
4508            .iter()
4509            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4510}
4511
4512fn type_candidate_visible_at_reference(
4513    candidate: &CodeUnit,
4514    reference: Node<'_>,
4515    ctx: &ScanCtx<'_>,
4516) -> bool {
4517    let class_owned_alias = ctx
4518        .analyzer
4519        .type_alias_provider()
4520        .is_some_and(|provider| provider.is_type_alias(candidate))
4521        && ctx
4522            .analyzer
4523            .parent_of(candidate)
4524            .is_some_and(|owner| owner.is_class());
4525    if class_owned_alias {
4526        let conditional_family = ctx
4527            .visibility
4528            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4529        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4530            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4531            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4532        let guard_match = ctx
4533            .visibility
4534            .external_type_candidate_guard_compatible_in_context(
4535                &ctx.analyzer,
4536                ctx.file,
4537                candidate,
4538                reference,
4539            );
4540        let general_match = conditional_family
4541            && ctx.visibility.external_type_candidate_visible_in_context(
4542                &ctx.analyzer,
4543                ctx.file,
4544                candidate,
4545                reference,
4546            );
4547        return owner_match && (guard_match || general_match);
4548    }
4549    ctx.visibility.external_type_candidate_visible_in_context(
4550        &ctx.analyzer,
4551        ctx.file,
4552        candidate,
4553        reference,
4554    )
4555}
4556
4557fn unqualified_reference_selects_inherited_alias(
4558    candidate: &CodeUnit,
4559    reference: Node<'_>,
4560    ctx: &ScanCtx<'_>,
4561) -> bool {
4562    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4563        return false;
4564    };
4565    if global || components.len() != 1 {
4566        return false;
4567    }
4568    matches!(
4569        resolve_type_node_lexically_for_target(
4570            reference,
4571            &ctx.analyzer,
4572            ctx.visibility,
4573            &ctx.ordinary_type_imports,
4574            ctx.file,
4575            ctx.source,
4576            candidate,
4577            Some(&ctx.lexical_scope_cache),
4578            ctx.recovered_sentinel_scope(reference).as_deref(),
4579        ),
4580        LexicalTypeResolution::Resolved {
4581            ref unit,
4582            ref candidates,
4583            ..
4584        } if ctx
4585            .visibility
4586            .same_template_member_identity(&ctx.analyzer, unit, candidate)
4587            || candidates.iter().any(|resolved| {
4588                ctx.visibility.same_template_member_identity(
4589                    &ctx.analyzer,
4590                    resolved,
4591                    candidate,
4592                )
4593            })
4594    )
4595}
4596
4597fn qualified_reference_selects_type_candidate(
4598    candidate: &CodeUnit,
4599    reference: Node<'_>,
4600    ctx: &ScanCtx<'_>,
4601) -> bool {
4602    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4603        return false;
4604    };
4605    if components.len() < 2 {
4606        return false;
4607    }
4608    let candidate_components = canonical_cpp_scope_components(candidate);
4609    match select_qualifier_owner(
4610        reference,
4611        global,
4612        &ctx.analyzer,
4613        ctx.visibility,
4614        &ctx.ordinary_type_imports,
4615        ctx.file,
4616        ctx.source,
4617    ) {
4618        QualifierOwnerSelection::Unchanged => {}
4619        QualifierOwnerSelection::Selected(selected) => {
4620            return selected == candidate_components;
4621        }
4622        QualifierOwnerSelection::Ambiguous => return false,
4623    }
4624    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4625        match enclosing_lexical_scope_components(
4626            reference,
4627            &ctx.analyzer,
4628            ctx.visibility,
4629            ctx.file,
4630            ctx.source,
4631        ) {
4632            LexicalScopeResolution::Resolved(scope) => scope,
4633            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4634                enclosing_namespace_components(reference, ctx.source)
4635            }
4636        }
4637    });
4638    lexical_component_tiers(&components, global, &lexical_scope)
4639        .any(|qualified| qualified == candidate_components)
4640}
4641
4642fn qualified_type_component_hit_node<'tree>(
4643    component: Node<'tree>,
4644    qualified: Node<'tree>,
4645) -> Node<'tree> {
4646    let mut current = component;
4647    while let Some(parent) = current.parent() {
4648        let is_type_name = matches!(
4649            parent.kind(),
4650            "template_type"
4651                | "qualified_identifier"
4652                | "scoped_identifier"
4653                | "scoped_type_identifier"
4654        ) && parent
4655            .child_by_field_name("name")
4656            .is_some_and(|name| same_node(name, current));
4657        if !is_type_name {
4658            break;
4659        }
4660        current = parent;
4661        if same_node(parent, qualified) {
4662            break;
4663        }
4664    }
4665    current
4666}
4667
4668fn template_type_component_preserves_target(
4669    node: Node<'_>,
4670    candidates: &[CodeUnit],
4671    ctx: &ScanCtx<'_>,
4672) -> bool {
4673    template_reference_candidates_select_target(
4674        node,
4675        candidates,
4676        &ctx.analyzer,
4677        ctx.visibility,
4678        ctx.file,
4679        ctx.source,
4680        &ctx.spec.target,
4681    )
4682}
4683
4684fn template_reference_candidates_select_target(
4685    node: Node<'_>,
4686    candidates: &[CodeUnit],
4687    analyzer: &CppGraphSource<'_>,
4688    visibility: &VisibilityIndex<'_>,
4689    file: &ProjectFile,
4690    source: &str,
4691    target: &CodeUnit,
4692) -> bool {
4693    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4694        return !visibility.is_template_specialization(target);
4695    };
4696    let direct_template_name =
4697        template_reference_name_node(node).map(|name| node_text(name, source));
4698    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4699        analyzer.type_alias_provider().is_some_and(|provider| {
4700            visibility
4701                .visible_identifier_candidates(file, name)
4702                .filter(|candidate| provider.is_type_alias(candidate))
4703                .any(|candidate| {
4704                    visibility.template_alias_arguments_preserve_target(
4705                        analyzer, file, candidate, &arguments, target,
4706                    )
4707                })
4708        })
4709    });
4710    named_alias_selects_target
4711        || candidates.iter().any(|candidate| {
4712            (same_visible_symbol(candidate, target)
4713                && visibility.is_primary_template(target)
4714                && direct_template_name == Some(candidate.identifier()))
4715                || visibility.template_alias_arguments_preserve_target(
4716                    analyzer, file, candidate, &arguments, target,
4717                )
4718                || visibility
4719                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4720                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4721        })
4722}
4723
4724fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4725    let template = if node.kind() == "template_type" {
4726        node
4727    } else {
4728        node.child_by_field_name("name")
4729            .filter(|name| name.kind() == "template_type")?
4730    };
4731    template.child_by_field_name("name")
4732}
4733
4734fn type_resolution_matches_target(
4735    node: Node<'_>,
4736    unit: &CodeUnit,
4737    candidates: &[CodeUnit],
4738    ctx: &ScanCtx<'_>,
4739) -> bool {
4740    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4741}
4742
4743fn type_resolution_matches_unit_target(
4744    node: Node<'_>,
4745    unit: &CodeUnit,
4746    candidates: &[CodeUnit],
4747    target: &CodeUnit,
4748    ctx: &ScanCtx<'_>,
4749) -> bool {
4750    target_alias_candidates_visible(candidates, node, ctx)
4751        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4752}
4753
4754/// The identity half of the type-resolution match, without the alias
4755/// visibility gate.
4756///
4757/// Use it only on the without-visibility fallback path, which reports an
4758/// unproven hit. An alias spelling does not contain the target identifier, so
4759/// the name-mention fallback can never recover a rejected alias reference: the
4760/// site would disappear instead of degrading to a reviewable hit.
4761fn type_resolution_identifies_unit_target(
4762    node: Node<'_>,
4763    unit: &CodeUnit,
4764    candidates: &[CodeUnit],
4765    target: &CodeUnit,
4766    ctx: &ScanCtx<'_>,
4767) -> bool {
4768    if !template_alias_owner_matches_reference(node, target, ctx) {
4769        return false;
4770    }
4771    if ctx.visibility.is_template_specialization(target)
4772        && cpp_template_reference_arguments(node, ctx.source).is_some()
4773    {
4774        let selected_unit =
4775            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4776                ctx.visibility
4777                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4778                    .ok()
4779            });
4780        return selected_unit
4781            .as_ref()
4782            .is_some_and(|selected| same_visible_symbol(selected, target))
4783            || template_reference_candidates_select_target(
4784                node,
4785                candidates,
4786                &ctx.analyzer,
4787                ctx.visibility,
4788                ctx.file,
4789                ctx.source,
4790                target,
4791            );
4792    }
4793    unit == target
4794        || ctx
4795            .visibility
4796            .same_template_member_identity(&ctx.analyzer, unit, target)
4797        || ctx.visibility.c_tag_declaration_family_matches_target(
4798            &ctx.analyzer,
4799            ctx.file,
4800            &candidates.iter().collect::<Vec<_>>(),
4801            target,
4802        )
4803        || ctx.visibility.structured_class_alias_resolves_to_target(
4804            &ctx.analyzer,
4805            ctx.file,
4806            unit,
4807            target,
4808        )
4809        || candidates.iter().any(|candidate| {
4810            ctx.visibility
4811                .same_template_member_identity(&ctx.analyzer, candidate, target)
4812                || ctx.visibility.structured_class_alias_resolves_to_target(
4813                    &ctx.analyzer,
4814                    ctx.file,
4815                    candidate,
4816                    target,
4817                )
4818        })
4819}
4820
4821/// Keep a member alias attached to the class specialization that declares it.
4822/// A target-guided lexical lookup can otherwise retain the primary alias when
4823/// the source reference is inside a partial specialization with the same
4824/// unqualified alias name. Compare the indexed template identities instead of
4825/// rendered text or suffixes.
4826fn template_alias_owner_matches_reference(
4827    node: Node<'_>,
4828    target: &CodeUnit,
4829    ctx: &ScanCtx<'_>,
4830) -> bool {
4831    if !ctx
4832        .analyzer
4833        .type_alias_provider()
4834        .is_some_and(|provider| provider.is_type_alias(target))
4835    {
4836        return true;
4837    }
4838    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4839        return true;
4840    };
4841    if !target_owner.is_class() {
4842        return true;
4843    }
4844    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4845        return true;
4846    };
4847    if !ctx.visibility.is_template_specialization(&target_owner)
4848        && !ctx.visibility.is_template_specialization(&reference_owner)
4849    {
4850        return true;
4851    }
4852    same_visible_symbol(&target_owner, &reference_owner)
4853}
4854
4855fn inherited_injected_class_qualifier_scope<'tree>(
4856    node: Node<'tree>,
4857    ctx: &ScanCtx<'_>,
4858) -> Option<Node<'tree>> {
4859    let qualified = qualified_owner_components(node, ctx.source)?;
4860    if qualified.global || qualified.names.is_empty() {
4861        return None;
4862    }
4863    let injected_name = &qualified.names[0];
4864    if !ctx.spec.target.is_class()
4865        || ctx.spec.target.identifier() != injected_name
4866        || physically_visible_type_target(ctx).is_none()
4867    {
4868        return None;
4869    }
4870    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4871    let owner = ctx.visibility.inherited_injected_class_owner(
4872        &ctx.analyzer,
4873        ctx.file,
4874        &enclosing_owner,
4875        injected_name,
4876    )?;
4877    same_visible_symbol(&owner, &ctx.spec.target)
4878        .then(|| qualified.nodes.first().copied())
4879        .flatten()
4880}
4881
4882/// Resolve each qualified type component against the inverse target while
4883/// preserving C++ lexical-tier precedence and structured alias identity.
4884fn target_guided_qualifier_type_scopes<'tree>(
4885    node: Node<'tree>,
4886    ctx: &ScanCtx<'_>,
4887) -> Option<Vec<Node<'tree>>> {
4888    if !matches!(
4889        node.kind(),
4890        "qualified_identifier" | "scoped_type_identifier"
4891    ) {
4892        return None;
4893    }
4894    let target = physically_visible_type_target(ctx)?;
4895    let qualified = qualified_owner_components(node, ctx.source)?;
4896    if !qualified.global
4897        && qualified
4898            .names
4899            .first()
4900            .is_some_and(|name| local_receiver_alias_type_node(node, name, ctx).is_some())
4901    {
4902        return static_qualifier_type_scopes_for_components(node, qualified, ctx);
4903    }
4904    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4905    // scope before falling back to suffix recovery.  A short unqualified
4906    // owner can have a same-spelled class in a nested namespace (for example
4907    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4908    // both as possible owners and then fails closed, even though the
4909    // translation unit's lexical scope selects the global class.  Keep the
4910    // suffix path for malformed namespace sentinels, where the parser does
4911    // not expose every indexed scope component.
4912    let lexical_scope = match enclosing_lexical_scope_components(
4913        node,
4914        &ctx.analyzer,
4915        ctx.visibility,
4916        ctx.file,
4917        ctx.source,
4918    ) {
4919        LexicalScopeResolution::Resolved(scope) => scope,
4920        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4921            enclosing_namespace_components(node, ctx.source)
4922        }
4923    };
4924    let indexed_owner_scope =
4925        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4926    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4927    let mut matches = Vec::new();
4928    for component_count in 1..=qualified.names.len() {
4929        let components = &qualified.names[..component_count];
4930        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4931            .collect::<Vec<_>>();
4932        let name = components.last()?;
4933        let mut candidates = Vec::new();
4934        let mut exact_candidates = Vec::new();
4935        for candidate in ctx
4936            .visibility
4937            .visible_identifier_candidates(ctx.file, name)
4938            .filter(|candidate| candidate.is_class())
4939            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4940        {
4941            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4942                brokk_bifrost_core::analyzer::Language::Cpp,
4943                &cpp_name_for(candidate),
4944            );
4945            if !candidate_components.ends_with(components)
4946                || candidates
4947                    .iter()
4948                    .any(|existing| same_logical_symbol(existing, candidate))
4949            {
4950                continue;
4951            }
4952            let exact_lexical_scope = lexical_tiers
4953                .iter()
4954                .any(|expected| expected == &candidate_components);
4955            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4956            let structured_owner_match = indexed_owner_scope
4957                .as_ref()
4958                .is_some_and(|owner| owner.starts_with(candidate_owner))
4959                || recovered_owner_scope
4960                    .as_ref()
4961                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4962            let class_alias_owner_match = ctx
4963                .analyzer
4964                .type_alias_provider()
4965                .is_some_and(|provider| provider.is_type_alias(candidate))
4966                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4967            let macro_namespace_owner_match = is_declaration_name(node)
4968                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4969            if qualified.global && !exact_lexical_scope {
4970                continue;
4971            }
4972            if components.len() == 1
4973                && candidate_components != components
4974                && !exact_lexical_scope
4975                && !structured_owner_match
4976                && !class_alias_owner_match
4977                && !macro_namespace_owner_match
4978            {
4979                continue;
4980            }
4981            candidates.push(candidate.clone());
4982            if exact_lexical_scope {
4983                exact_candidates.push(candidate.clone());
4984            }
4985        }
4986        if !exact_candidates.is_empty() {
4987            candidates = exact_candidates;
4988        }
4989        // A typedef spelling can qualify nested C++ members while forward
4990        // lookup canonicalizes that spelling to its underlying class. Preserve
4991        // the exact alias prefix only when structured alias resolution proves
4992        // that it denotes this inverse target.
4993        let canonical_alias_target_matches = matches!(
4994            candidates.as_slice(),
4995            [candidate]
4996                if ctx
4997                    .analyzer
4998                    .type_alias_provider()
4999                    .is_some_and(|provider| provider.is_type_alias(candidate))
5000                    && type_candidate_visible_at_reference(candidate, node, ctx)
5001                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
5002                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5003                        brokk_bifrost_core::analyzer::Language::Cpp,
5004                        &cpp_name_for(candidate),
5005                    ) == components
5006                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
5007        );
5008        let direct_alias_target = ctx
5009            .analyzer
5010            .type_alias_provider()
5011            .is_some_and(|provider| provider.is_type_alias(target))
5012            && candidates
5013                .iter()
5014                .any(|candidate| same_symbol(candidate, target));
5015        let unique_target = matches!(
5016            candidates.as_slice(),
5017            [candidate] if same_visible_symbol(candidate, target)
5018        );
5019        if direct_alias_target || unique_target || canonical_alias_target_matches {
5020            let matched = if ctx
5021                .analyzer
5022                .type_alias_provider()
5023                .is_some_and(|provider| provider.is_type_alias(target))
5024                && ctx
5025                    .analyzer
5026                    .parent_of(target)
5027                    .is_some_and(|owner| owner.is_class())
5028                && ctx
5029                    .visibility
5030                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
5031                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
5032            {
5033                // The alias declaration owns the terminal component. Keep
5034                // the inverse range narrow so `MathLib::bigint` records the
5035                // `bigint` token, not the complete qualified owner path.
5036                qualified.nodes[component_count - 1]
5037            } else {
5038                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
5039            };
5040            if template_type_component_preserves_target(matched, &candidates, ctx) {
5041                matches.push(matched);
5042            }
5043        }
5044    }
5045    (!matches.is_empty()).then_some(matches)
5046}
5047
5048fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
5049    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5050        return false;
5051    };
5052    ctx.visibility
5053        .visible_identifier_candidates(ctx.file, target.identifier())
5054        .any(|candidate| {
5055            alias_provider.is_type_alias(candidate)
5056                && candidate.identifier() == target.identifier()
5057                && !same_visible_symbol(candidate, target)
5058                && ctx
5059                    .analyzer
5060                    .parent_of(candidate)
5061                    .is_some_and(|owner| owner.is_class())
5062        })
5063}
5064
5065/// Recover an out-of-line owner when the owner declaration and the reference
5066/// use different unknown preprocessor guards. Keep this result unproven.
5067fn target_guided_unproven_out_of_line_owner<'tree>(
5068    node: Node<'tree>,
5069    ctx: &ScanCtx<'_>,
5070) -> Option<Node<'tree>> {
5071    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
5072        || !is_declaration_name(node)
5073    {
5074        return None;
5075    }
5076    let target = physically_visible_type_target(ctx)?;
5077    if !target.is_class() {
5078        return None;
5079    }
5080    let qualified = qualified_owner_components(node, ctx.source)?;
5081    let target_components = canonical_cpp_scope_components(target);
5082    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
5083    let parser_scope = enclosing_namespace_components(node, ctx.source);
5084    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
5085        if !parser_scope.is_empty() || target_namespace.is_empty() {
5086            Some(parser_scope)
5087        } else {
5088            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5089        }
5090    })?;
5091    if has_malformed_wrapper_function_definition_ancestor(node)
5092        && target_namespace.starts_with(&scope)
5093        && target_namespace.len() > scope.len()
5094    {
5095        scope = target_namespace.to_vec();
5096    }
5097    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
5098        .any(|components| components == target_components)
5099    {
5100        return None;
5101    }
5102    let owner_name = qualified.names.last()?;
5103    let candidates = ctx
5104        .visibility
5105        .visible_identifier_candidates(ctx.file, owner_name)
5106        .filter(|candidate| {
5107            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
5108        })
5109        .collect::<Vec<_>>();
5110    if candidates.is_empty()
5111        || candidates
5112            .iter()
5113            .any(|candidate| !same_visible_symbol(candidate, target))
5114    {
5115        return None;
5116    }
5117    qualified.nodes.last().copied()
5118}
5119
5120fn macro_namespace_scope_matches(
5121    candidate_owner: &[String],
5122    node: Node<'_>,
5123    ctx: &ScanCtx<'_>,
5124) -> bool {
5125    let namespace = enclosing_namespace_components(node, ctx.source);
5126    if namespace.is_empty() || candidate_owner.is_empty() {
5127        return false;
5128    }
5129    let mut expanded_owner = Vec::new();
5130    for component in candidate_owner {
5131        if let Some(replacement) =
5132            ctx.visibility
5133                .object_macro_replacement_at(ctx.file, component, node.start_byte())
5134        {
5135            let replacement_components =
5136                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5137                    brokk_bifrost_core::analyzer::Language::Cpp,
5138                    &replacement,
5139                );
5140            if replacement_components.is_empty() {
5141                return false;
5142            }
5143            expanded_owner.extend(replacement_components);
5144        } else {
5145            expanded_owner.push(component.clone());
5146        }
5147    }
5148    expanded_owner == namespace
5149}
5150
5151fn target_guided_missing_type_leaf<'tree>(
5152    node: Node<'tree>,
5153    ctx: &ScanCtx<'_>,
5154) -> Option<Node<'tree>> {
5155    physically_visible_type_target(ctx)?;
5156    target_guided_missing_dependent_nested_type_leaf(node, ctx)
5157        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
5158        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
5159        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
5160        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
5161        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
5162}
5163
5164/// Recover a bare class-owned alias whose structured canonical target is the
5165/// requested type. The class owner must enclose the reference, and every alias
5166/// with that spelling in the owner chain must preserve the same target.
5167fn target_guided_missing_class_alias_target_type_leaf<'tree>(
5168    node: Node<'tree>,
5169    ctx: &ScanCtx<'_>,
5170) -> Option<Node<'tree>> {
5171    if node.kind() != "type_identifier"
5172        || is_declaration_name(node)
5173        || local_type_name_shadows(node, ctx)
5174    {
5175        return None;
5176    }
5177    let alias_provider = ctx.analyzer.type_alias_provider()?;
5178    let name = node_text(node, ctx.source);
5179    let aliases = ctx
5180        .visibility
5181        .visible_identifier_candidates(ctx.file, name)
5182        .filter(|candidate| alias_provider.is_type_alias(candidate))
5183        .filter(|candidate| {
5184            ctx.analyzer
5185                .parent_of(candidate)
5186                .is_some_and(|owner| owner.is_class())
5187        })
5188        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5189        .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5190        .collect::<Vec<_>>();
5191    (!aliases.is_empty()
5192        && aliases.iter().all(|candidate| {
5193            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5194        }))
5195    .then_some(node)
5196}
5197
5198/// Recover an ambiguous unqualified alias used by a parameter or placement-new
5199/// type only when the indexed class owner proves the alias declaration. This
5200/// narrow path covers malformed class bodies without accepting unrelated aliases.
5201fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5202    node: Node<'tree>,
5203    ctx: &ScanCtx<'_>,
5204) -> Option<Node<'tree>> {
5205    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5206        matches!(
5207            declaration.kind(),
5208            "parameter_declaration" | "optional_parameter_declaration"
5209        )
5210    });
5211    let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5212        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5213    });
5214    if !parameter && !placement_new_type {
5215        return None;
5216    }
5217    if !ctx
5218        .analyzer
5219        .type_alias_provider()
5220        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5221        || !type_alias_owner_matches_structured_reference(node, ctx)
5222    {
5223        return None;
5224    }
5225    target_guided_missing_declaration_type_leaf(node, ctx)
5226}
5227
5228/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
5229/// sentinel prevents ordinary lexical resolution. The indexed target must have
5230/// an indexed class parent, the structured owner path must compose with one
5231/// proven lexical namespace source, and every visible candidate at that exact
5232/// owner path must be the indexed parent. This keeps the fallback owner-based;
5233/// a same-spelled nested type under another template remains unproven.
5234fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5235    node: Node<'tree>,
5236    ctx: &ScanCtx<'_>,
5237) -> Option<Node<'tree>> {
5238    if !matches!(
5239        node.kind(),
5240        "qualified_identifier" | "scoped_type_identifier"
5241    ) || !qualified_type_scope_contains_template(node)
5242    {
5243        return None;
5244    }
5245    let name = node
5246        .child_by_field_name("name")
5247        .filter(|name| name.kind() == "type_identifier")?;
5248    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5249        return None;
5250    }
5251    // Template arguments that select a specialization, or that cannot be
5252    // bound, decide the owner; the owner-spelling fallback below reads the
5253    // qualifier without them and would name the primary template (#6902).
5254    if !matches!(
5255        select_qualifier_owner(
5256            node,
5257            is_globally_qualified_cpp_name(node),
5258            &ctx.analyzer,
5259            ctx.visibility,
5260            &ctx.ordinary_type_imports,
5261            ctx.file,
5262            ctx.source,
5263        ),
5264        QualifierOwnerSelection::Unchanged
5265    ) {
5266        return None;
5267    }
5268    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5269    if !owner_target.is_class() {
5270        return None;
5271    }
5272    let owner = node.child_by_field_name("scope")?;
5273    let owner_resolution = resolve_type_node_lexically_for_target(
5274        owner,
5275        &ctx.analyzer,
5276        ctx.visibility,
5277        &ctx.ordinary_type_imports,
5278        ctx.file,
5279        ctx.source,
5280        &owner_target,
5281        Some(&ctx.lexical_scope_cache),
5282        ctx.recovered_sentinel_scope(owner).as_deref(),
5283    );
5284    if matches!(
5285        owner_resolution,
5286        LexicalTypeResolution::Resolved {
5287            ref unit,
5288            ref candidates,
5289            ..
5290        } if type_resolution_matches_unit_target(
5291            owner,
5292            unit,
5293            candidates,
5294            &owner_target,
5295            ctx,
5296        )
5297    ) {
5298        return Some(name);
5299    }
5300
5301    let qualified = qualified_owner_components(node, ctx.source)?;
5302    let namespace = ctx
5303        .lexical_scope_cache
5304        .orphaned
5305        .enclosing_namespace_components(node, ctx.source);
5306    let indexed_scope = ctx
5307        .recovered_sentinel_scope(node)
5308        .or_else(|| (!namespace.is_empty()).then_some(namespace))
5309        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5310    let owner_components = canonical_cpp_scope_components(&owner_target);
5311    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5312        .any(|components| components == owner_components)
5313    {
5314        return None;
5315    }
5316    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5317        .into_iter()
5318        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5319        .collect::<Vec<_>>();
5320    (!scoped_candidates.is_empty()
5321        && scoped_candidates
5322            .iter()
5323            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5324    .then_some(name)
5325}
5326
5327/// Recover a nested type-alias reference when parser recovery leaves an
5328/// unqualified template argument under a member function.  The ordinary
5329/// lexical lookup can select a same-spelled namespace alias (or fail closed)
5330/// even though the indexed callable owner proves that the reference is inside
5331/// the class which declares the target alias.
5332fn target_guided_missing_member_alias_type_leaf<'tree>(
5333    node: Node<'tree>,
5334    ctx: &ScanCtx<'_>,
5335) -> Option<Node<'tree>> {
5336    if !is_cpp_template_argument_type_leaf(node)
5337        || is_declaration_name(node)
5338        || ctx
5339            .target_declaration_ranges
5340            .iter()
5341            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5342        || node_text(node, ctx.source) != ctx.spec.target.identifier()
5343        || local_type_name_shadows(node, ctx)
5344        || !ctx
5345            .analyzer
5346            .type_alias_provider()
5347            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5348    {
5349        return None;
5350    }
5351    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5352    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5353    if !owner_scope_matches
5354        && !indexed_scope.is_some_and(|scope| {
5355            indexed_scope_matches_target_name(
5356                &scope,
5357                &[ctx.spec.target.identifier().to_string()],
5358                false,
5359                &ctx.spec.target,
5360            )
5361        })
5362    {
5363        return None;
5364    }
5365    // The indexed symbol table intentionally retains declarations from every
5366    // preprocessor branch and from later source positions.  The recovered
5367    // class-owner scope proves the spelling, but it does not prove that this
5368    // alias was active and introduced before the reference.  Apply the same
5369    // structured guard/source-order check used by the ordinary resolver before
5370    // turning the target-guided recovery into a proven hit.
5371    if !(ctx.visibility.external_type_candidate_visible_in_context(
5372        &ctx.analyzer,
5373        ctx.file,
5374        &ctx.spec.target,
5375        node,
5376    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5377    {
5378        return None;
5379    }
5380    let target_visible = ctx
5381        .visibility
5382        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5383        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5384    target_visible.then_some(node)
5385}
5386
5387/// Recover a class/enum template argument only when the parser's ordinary
5388/// lexical lookup failed but the indexed scope and visible declaration still
5389/// prove the exact target. This intentionally excludes aliases: an alias
5390/// argument needs its own template-argument selection path, while a direct
5391/// class/enum argument can be identified by its canonical scope and symbol.
5392fn target_guided_missing_template_argument_type_leaf<'tree>(
5393    node: Node<'tree>,
5394    ctx: &ScanCtx<'_>,
5395) -> Option<Node<'tree>> {
5396    let target = &ctx.spec.target;
5397    let name = node_text(node, ctx.source);
5398    if !target.is_class()
5399        || !is_cpp_template_argument_type_leaf(node)
5400        || is_declaration_name(node)
5401        || name != target.identifier()
5402        || ctx.local_shadows.is_shadowed(name)
5403        || local_type_name_shadows(node, ctx)
5404        || !ctx.visibility.is_physically_visible(ctx.file, target)
5405        || ctx
5406            .analyzer
5407            .type_alias_provider()
5408            .is_some_and(|provider| provider.is_type_alias(target))
5409    {
5410        return None;
5411    }
5412
5413    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5414    let target_components = canonical_cpp_scope_components(target);
5415    if target_components.last().map(String::as_str) != Some(name)
5416        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5417            .any(|components| components == target_components)
5418    {
5419        return None;
5420    }
5421
5422    // The direct visible class candidate supplies the declaration identity;
5423    // the scope check above supplies its canonical owner path. Do not let an
5424    // alias or a same-scoped competing class enter this recovery path.
5425    let candidates = visible_type_identifier_candidates(ctx, name);
5426    if candidates.is_empty()
5427        || candidates.iter().any(|candidate| {
5428            !candidate.is_class()
5429                || ctx
5430                    .analyzer
5431                    .type_alias_provider()
5432                    .is_some_and(|provider| provider.is_type_alias(candidate))
5433                || (!same_visible_symbol(candidate, target)
5434                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5435                        .any(|components| components == canonical_cpp_scope_components(candidate)))
5436        })
5437        || !candidates
5438            .iter()
5439            .any(|candidate| same_visible_symbol(candidate, target))
5440    {
5441        return None;
5442    }
5443
5444    // Physical visibility covers the file/import projection; this second
5445    // guard preserves declaration ordering and preprocessor branch identity.
5446    ctx.visibility
5447        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5448        .then_some(node)
5449}
5450
5451/// Recover the class owner of an out-of-line member whose trailing attribute
5452/// macro was parsed as a separate function definition around the real body.
5453fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5454    let mut function = node;
5455    while function.kind() != "function_definition" {
5456        function = ctx.ancestry.parent(function)?;
5457    }
5458    let macro_name = function_definition_name_node(function)?;
5459    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5460        return None;
5461    }
5462
5463    // An unknown trailing attribute macro can split one real definition into
5464    // a missing-semicolon declaration for `Owner::method()` and an adjacent
5465    // macro-named function definition that owns the body. Recover only that
5466    // exact CST sequence; a complete declaration or a non-macro function is
5467    // an ordinary independent construct.
5468    let declaration = function.prev_named_sibling()?;
5469    if declaration.kind() != "declaration"
5470        || !declaration.has_error()
5471        || function.start_position().row > declaration.end_position().row + 1
5472    {
5473        return None;
5474    }
5475    let mut missing_semicolon = false;
5476    let mut real_semicolon = false;
5477    for child in children_iter(declaration) {
5478        if child.kind() == ";" {
5479            missing_semicolon |= child.is_missing();
5480            real_semicolon |= !child.is_missing();
5481        }
5482    }
5483    if !missing_semicolon || real_semicolon {
5484        return None;
5485    }
5486
5487    let initializer = declaration.child_by_field_name("declarator")?;
5488    if initializer.kind() != "init_declarator"
5489        || initializer
5490            .child_by_field_name("value")
5491            .is_none_or(|value| value.kind() != "argument_list")
5492    {
5493        return None;
5494    }
5495    let qualified_name = initializer
5496        .child_by_field_name("declarator")
5497        .and_then(declarator_name_node)?;
5498    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5499    let lexical_scope = enclosing_namespace_components(function, ctx.source);
5500    match ctx.visibility.resolve_type_components_lexically(
5501        &ctx.analyzer,
5502        ctx.file,
5503        &qualified.names,
5504        qualified.global,
5505        &lexical_scope,
5506    ) {
5507        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5508        LexicalTypeResolution::Resolved { .. }
5509        | LexicalTypeResolution::Ambiguous
5510        | LexicalTypeResolution::Missing => None,
5511    }
5512}
5513
5514/// A class member alias is visible throughout its complete class scope, even
5515/// when its declaration byte follows a recovered out-of-line member's
5516/// trailing return type. Match the indexed owner path structurally before
5517/// allowing the guard-only visibility check above to waive source ordering.
5518fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5519    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5520}
5521
5522fn member_alias_owner_matches_reference_for(
5523    target: &CodeUnit,
5524    node: Node<'_>,
5525    ctx: &ScanCtx<'_>,
5526) -> bool {
5527    let Some(owner) = ctx.analyzer.parent_of(target) else {
5528        return false;
5529    };
5530    if !owner.is_class() {
5531        return false;
5532    }
5533    let reference_owner = ctx
5534        .class_ranges
5535        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5536        .or_else(|| structured_enclosing_owner(node, ctx));
5537    if reference_owner.as_ref().is_some_and(|reference_owner| {
5538        ctx.visibility
5539            .same_template_owner_identity(&owner, reference_owner)
5540    }) {
5541        return true;
5542    }
5543    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5544        ctx.visibility
5545            .same_template_owner_identity(&owner, &reference_owner)
5546    }) {
5547        return true;
5548    }
5549    if reference_owner.is_some_and(|reference_owner| {
5550        matches!(
5551            resolve_declaring_member_owner(
5552                &ctx.analyzer,
5553                ctx.visibility,
5554                ctx.file,
5555                &reference_owner,
5556                target.identifier(),
5557            ),
5558            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5559                if ctx
5560                    .visibility
5561                    .same_template_owner_identity(&owner, &declaring_owner)
5562        )
5563    }) {
5564        return true;
5565    }
5566    let range = Range {
5567        start_byte: node.start_byte(),
5568        end_byte: node.end_byte(),
5569        start_line: node.start_position().row + 1,
5570        end_line: node.end_position().row + 1,
5571    };
5572    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5573    while let Some(candidate) = indexed_enclosing {
5574        if candidate.is_class()
5575            && ctx
5576                .visibility
5577                .same_template_owner_identity(&owner, &candidate)
5578        {
5579            return true;
5580        }
5581        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5582    }
5583    if let Some(reference_body) = malformed_recovered_class_body(node) {
5584        let mut root = node;
5585        while let Some(parent) = ctx.ancestry.parent(root) {
5586            root = parent;
5587        }
5588        if ctx.analyzer.ranges(target).iter().any(|range| {
5589            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5590                .and_then(malformed_recovered_class_body)
5591                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5592        }) {
5593            return true;
5594        }
5595    }
5596    if structured_enclosing_owner(node, ctx)
5597        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5598    {
5599        return true;
5600    }
5601    let owner_components = canonical_cpp_scope_components(&owner);
5602    if ctx
5603        .recovered_sentinel_scope(node)
5604        .is_some_and(|scope| scope == owner_components)
5605    {
5606        return true;
5607    }
5608    if matches!(
5609        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5610            node,
5611            &ctx.analyzer,
5612            ctx.visibility,
5613            ctx.file,
5614            ctx.source,
5615            false,
5616            false,
5617            Some(&ctx.lexical_scope_cache),
5618        ),
5619        LexicalScopeResolution::Resolved(reference_scope)
5620            if reference_scope == owner_components
5621    ) {
5622        return true;
5623    }
5624    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5625    else {
5626        return false;
5627    };
5628    !owner_components.is_empty() && reference_scope == owner_components
5629}
5630
5631fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5632    loop {
5633        if node.kind() == "compound_statement"
5634            && node
5635                .parent()
5636                .is_some_and(|parent| parent.kind() == "declaration_list")
5637            && node.prev_named_sibling().is_some_and(|header| {
5638                header.kind() == "ERROR"
5639                    && header.end_byte() <= node.start_byte()
5640                    && error_contains_class_header(header)
5641            })
5642        {
5643            return Some(node);
5644        }
5645        node = node.parent()?;
5646    }
5647}
5648
5649/// Whether a shallow parser error contains a recognizable class header.
5650pub fn error_contains_class_header(node: Node<'_>) -> bool {
5651    let mut pending = vec![(node, 0usize)];
5652    while let Some((current, depth)) = pending.pop() {
5653        if matches!(current.kind(), "class" | "struct" | "union") {
5654            let mut sibling = current.next_sibling();
5655            let mut saw_name = false;
5656            while let Some(candidate) = sibling {
5657                match candidate.kind() {
5658                    "comment" => {}
5659                    "{" | "base_class_clause" | ":" => return saw_name,
5660                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5661                    _ if !candidate.is_named() => {}
5662                    _ => break,
5663                }
5664                sibling = candidate.next_sibling();
5665            }
5666        }
5667        if depth >= 1 {
5668            continue;
5669        }
5670        let mut cursor = current.walk();
5671        pending.extend(
5672            current
5673                .children(&mut cursor)
5674                .filter(|child| child.kind() != "compound_statement")
5675                .map(|child| (child, depth + 1)),
5676        );
5677    }
5678    false
5679}
5680
5681fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5682    has_ancestor_kind(node, "compound_statement")
5683        && ctx
5684            .visibility
5685            .external_type_candidate_guard_compatible_in_context(
5686                &ctx.analyzer,
5687                ctx.file,
5688                &ctx.spec.target,
5689                node,
5690            )
5691}
5692
5693fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5694    ctx.analyzer
5695        .type_alias_provider()
5696        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5697        && member_alias_owner_matches_reference(node, ctx)
5698}
5699
5700/// A nested class can use aliases declared by any enclosing class. Preserve
5701/// that structured owner chain for malformed macro-return nodes, whose phantom
5702/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5703fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5704    if !ctx
5705        .analyzer
5706        .type_alias_provider()
5707        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5708    {
5709        return false;
5710    }
5711    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5712        return false;
5713    };
5714    let mut reference_owner = structured_enclosing_owner(node, ctx);
5715    while let Some(owner) = reference_owner {
5716        if same_logical_symbol(&target_owner, &owner) {
5717            return true;
5718        }
5719        reference_owner = ctx.analyzer.parent_of(&owner);
5720    }
5721    false
5722}
5723
5724/// An enclosing class alias is only usable when no nearer class declares the
5725/// same type name. The recovered macro-return path does not have a complete
5726/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5727/// rule before the enclosing alias fast path runs.
5728fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5729    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5730        return false;
5731    };
5732    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5733        return false;
5734    };
5735    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5736        return false;
5737    };
5738    let candidates = ctx
5739        .visibility
5740        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5741        .filter(|candidate| {
5742            candidate.is_class()
5743                && alias_provider.is_type_alias(candidate)
5744                && !same_visible_symbol(candidate, &ctx.spec.target)
5745        })
5746        .cloned()
5747        .collect::<Vec<_>>();
5748
5749    let mut owner = Some(reference_owner);
5750    while let Some(owner_unit) = owner {
5751        if same_logical_symbol(&target_owner, &owner_unit) {
5752            return false;
5753        }
5754        if candidates.iter().any(|candidate| {
5755            ctx.analyzer
5756                .parent_of(candidate)
5757                .is_some_and(|candidate_owner| {
5758                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5759                })
5760                && ctx
5761                    .visibility
5762                    .external_type_candidate_guard_compatible_in_context(
5763                        &ctx.analyzer,
5764                        ctx.file,
5765                        candidate,
5766                        node,
5767                    )
5768        }) {
5769            return true;
5770        }
5771        owner = ctx.analyzer.parent_of(&owner_unit);
5772    }
5773    false
5774}
5775
5776/// Whether a type spelling can name a function-local class target at all.
5777///
5778/// Only block-scope lookup of an unqualified head reaches a local class, so
5779/// the spelling must not be `::`-rooted and its head must resolve to the
5780/// target or to a local class that encloses it. Anything else names some
5781/// other type, and neither a proven nor an unproven hit may claim it. Every
5782/// other target is unaffected.
5783fn local_class_target_reachable(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5784    if !ctx.spec.target.is_class() || !is_function_local_unit(&ctx.spec.target) {
5785        return true;
5786    }
5787    type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
5788        !global
5789            && matches!(
5790                function_local_type_resolution(
5791                    &ctx.analyzer,
5792                    ctx.file,
5793                    node,
5794                    &components[..1],
5795                    ctx.source,
5796                ),
5797                Some(LexicalTypeResolution::Resolved { unit, .. })
5798                    if ctx.spec.target.fq().starts_with(unit.fq())
5799            )
5800    })
5801}
5802
5803fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5804    if cpp_active_template_type_parameter(
5805        node,
5806        ctx.spec.target.identifier(),
5807        ctx.source,
5808        &ctx.ancestry,
5809    ) {
5810        return true;
5811    }
5812    // A function-local class target is itself one of the local declarations
5813    // the walk below treats as shadows. A spelling reaches a local class only
5814    // through block-scope lookup of its head: a `::`-rooted name never does,
5815    // and an unqualified name or a qualifier head is shadowed exactly when
5816    // that lookup names another declaration. A later component of a qualified
5817    // name is decided by ordinary resolution, which applies the same lookup
5818    // to its head.
5819    if ctx.spec.target.is_class() && is_function_local_unit(&ctx.spec.target) {
5820        let qualifier = ctx.ancestry.parent(node).filter(|parent| {
5821            matches!(
5822                parent.kind(),
5823                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5824            )
5825        });
5826        if let Some(qualifier) = qualifier {
5827            let scope = qualifier.child_by_field_name("scope");
5828            if scope.is_none() {
5829                return true;
5830            }
5831            let is_head = scope == Some(node)
5832                && ctx.ancestry.parent(qualifier).is_none_or(|outer| {
5833                    !matches!(
5834                        outer.kind(),
5835                        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
5836                    ) || outer.child_by_field_name("scope") == Some(qualifier)
5837                });
5838            if !is_head {
5839                return false;
5840            }
5841        }
5842        return !matches!(
5843            function_local_type_resolution(
5844                &ctx.analyzer,
5845                ctx.file,
5846                node,
5847                &[ctx.spec.target.identifier().to_string()],
5848                ctx.source,
5849            ),
5850            Some(LexicalTypeResolution::Resolved { unit, .. })
5851                if same_visible_symbol(&unit, &ctx.spec.target)
5852        );
5853    }
5854    let Some(callable) = nearest_callable_scope(node) else {
5855        return false;
5856    };
5857    let mut root_callable = callable;
5858    let mut ancestor = ctx.ancestry.parent(callable);
5859    while let Some(current) = ancestor {
5860        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5861            root_callable = current;
5862        }
5863        ancestor = ctx.ancestry.parent(current);
5864    }
5865
5866    let mut stack = vec![root_callable];
5867    while let Some(current) = stack.pop() {
5868        if current.start_byte() >= node.start_byte() {
5869            continue;
5870        }
5871        if let Some(name) = local_type_name_declaration_node(current)
5872            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5873            && nearest_callable_scope(current).is_some_and(|owner| {
5874                !is_malformed_wrapper_function_definition(owner)
5875                    && owner.start_byte() <= callable.start_byte()
5876                    && callable.end_byte() <= owner.end_byte()
5877            })
5878            && local_alias_scope_contains_node(current, node)
5879        {
5880            return true;
5881        }
5882        let mut cursor = current.walk();
5883        stack.extend(current.named_children(&mut cursor));
5884    }
5885    false
5886}
5887
5888fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5889    local_type_alias_name_node(node).or_else(|| match node.kind() {
5890        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5891            .child_by_field_name("name")
5892            .filter(|name| is_declaration_name(*name)),
5893        _ => None,
5894    })
5895}
5896
5897fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5898    loop {
5899        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5900            return Some(node);
5901        }
5902        node = node.parent()?;
5903    }
5904}
5905
5906fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5907    match node.kind() {
5908        "alias_declaration" => node.child_by_field_name("name"),
5909        "type_definition" => node
5910            .child_by_field_name("declarator")
5911            .and_then(declarator_name_node),
5912        _ => None,
5913    }
5914}
5915
5916fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5917    let mut current = alias.parent();
5918    while let Some(parent) = current {
5919        if matches!(
5920            parent.kind(),
5921            "class_specifier" | "struct_specifier" | "union_specifier"
5922        ) {
5923            return false;
5924        }
5925        if parent.kind() == "compound_statement" {
5926            return parent.start_byte() <= node.start_byte()
5927                && node.end_byte() <= parent.end_byte();
5928        }
5929        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5930            let Some(body) = parent.child_by_field_name("body") else {
5931                return false;
5932            };
5933            return node_is_within(body, alias) && node_is_within(body, node);
5934        }
5935        current = parent.parent();
5936    }
5937    false
5938}
5939
5940fn target_guided_missing_declaration_type_leaf<'tree>(
5941    node: Node<'tree>,
5942    ctx: &ScanCtx<'_>,
5943) -> Option<Node<'tree>> {
5944    if is_declaration_name(node) {
5945        return None;
5946    }
5947    let component_nodes = cpp_name_component_nodes(node)?;
5948    let name_node = component_nodes.last().copied()?;
5949    let name = node_text(name_node, ctx.source);
5950    if name != ctx.spec.target.identifier() {
5951        return None;
5952    }
5953    let inside_target_declaration = ctx
5954        .target_declaration_ranges
5955        .iter()
5956        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5957    if !inside_target_declaration
5958        && !ctx.visibility.external_type_candidate_visible_in_context(
5959            &ctx.analyzer,
5960            ctx.file,
5961            &ctx.spec.target,
5962            node,
5963        )
5964    {
5965        return None;
5966    }
5967    let components = component_nodes
5968        .iter()
5969        .map(|component| node_text(*component, ctx.source).to_string())
5970        .collect::<Vec<_>>();
5971    let local_alias_shadow = local_type_name_shadows(node, ctx);
5972    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5973    let indexed_alias_owner = ctx
5974        .analyzer
5975        .type_alias_provider()
5976        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5977        && member_alias_owner_matches_reference(node, ctx);
5978    let target_alias_self_reference = inside_target_declaration
5979        && ctx
5980            .analyzer
5981            .type_alias_provider()
5982            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5983    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5984        &ctx.analyzer,
5985        ctx.file,
5986        &ctx.spec.target,
5987        node,
5988    ) || member_alias_complete_class_context(node, ctx);
5989    if !target_alias_self_reference
5990        && !local_alias_shadow
5991        && member_alias_visible
5992        && (structured_alias_owner || indexed_alias_owner)
5993    {
5994        return Some(node);
5995    }
5996    let declaration = nearest_declaration_type_context(node)?;
5997    let candidates = visible_type_identifier_candidates(ctx, name);
5998    let unique_visible_target = !candidates.is_empty()
5999        && candidates
6000            .iter()
6001            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
6002    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
6003    let exact_scope_match = indexed_scope_matches_target_name(
6004        &indexed_scope,
6005        &components,
6006        is_globally_qualified_cpp_name(node),
6007        &ctx.spec.target,
6008    );
6009    if matches!(declaration.kind(), "field_declaration" | "declaration") {
6010        let parser_lost_declaration_scope =
6011            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
6012                && unique_visible_target;
6013        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
6014    }
6015    let lost_namespace_parameter_context =
6016        matches!(
6017            declaration.kind(),
6018            "parameter_declaration" | "optional_parameter_declaration"
6019        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
6020    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
6021        return Some(node);
6022    }
6023    None
6024}
6025
6026fn target_guided_missing_alias_rhs_type_leaf<'tree>(
6027    node: Node<'tree>,
6028    ctx: &ScanCtx<'_>,
6029) -> Option<Node<'tree>> {
6030    let mut stack = vec![node];
6031    while let Some(candidate) = stack.pop() {
6032        if candidate.kind() == "type_identifier"
6033            && !is_declaration_name(candidate)
6034            && matches!(
6035                ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
6036                Some("template_type")
6037            )
6038        {
6039            let mut current = ctx.ancestry.parent(candidate);
6040            let mut saw_qualified = false;
6041            let mut saw_dependent = false;
6042            let mut saw_type_descriptor = false;
6043            let mut saw_alias_declaration = false;
6044            while let Some(ancestor) = current {
6045                match ancestor.kind() {
6046                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
6047                    "dependent_type" => saw_dependent = true,
6048                    "type_descriptor" => saw_type_descriptor = true,
6049                    "alias_declaration" => {
6050                        saw_alias_declaration = true;
6051                        break;
6052                    }
6053                    "template_type"
6054                    | "template_argument_list"
6055                    | "typename"
6056                    | "template_declaration" => {}
6057                    _ => {}
6058                }
6059                current = ctx.ancestry.parent(ancestor);
6060            }
6061            let name = node_text(candidate, ctx.source);
6062            let visible_candidates = visible_type_identifier_candidates(ctx, name);
6063            let canonical_alias_target = visible_candidates
6064                .iter()
6065                .filter_map(|alias| ctx.visibility.alias_target(alias))
6066                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
6067            let alias_resolves =
6068                ctx.visibility
6069                    .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
6070                    || canonical_alias_target;
6071            if saw_qualified
6072                && saw_dependent
6073                && saw_type_descriptor
6074                && saw_alias_declaration
6075                && alias_resolves
6076                && ctx.visibility.external_type_candidate_visible_in_context(
6077                    &ctx.analyzer,
6078                    ctx.file,
6079                    &ctx.spec.target,
6080                    candidate,
6081                )
6082            {
6083                return Some(candidate);
6084            }
6085        }
6086        push_named_children_reversed(candidate, &mut stack);
6087    }
6088    None
6089}
6090
6091fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
6092    let mut current = Some(node);
6093    while let Some(ancestor) = current {
6094        if matches!(
6095            ancestor.kind(),
6096            "field_declaration"
6097                | "parameter_declaration"
6098                | "optional_parameter_declaration"
6099                | "declaration"
6100                | "type_descriptor"
6101        ) {
6102            let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
6103            if contains_type
6104                && !(ancestor.kind() == "type_descriptor"
6105                    && is_cpp_template_argument_type_leaf(node))
6106            {
6107                return Some(ancestor);
6108            }
6109            if ancestor.kind() == "type_descriptor"
6110                && ancestor.parent().is_some_and(|parent| {
6111                    matches!(
6112                        parent.kind(),
6113                        "cast_expression"
6114                            | "new_expression"
6115                            | "sizeof_expression"
6116                            | "alignof_expression"
6117                            | "typeid_expression"
6118                    )
6119                })
6120            {
6121                return Some(ancestor);
6122            }
6123        }
6124        if matches!(
6125            ancestor.kind(),
6126            "compound_statement"
6127                | "translation_unit"
6128                | "namespace_definition"
6129                | "alias_declaration"
6130                | "type_definition"
6131                | "base_class_clause"
6132        ) {
6133            return None;
6134        }
6135        current = ancestor.parent();
6136    }
6137    None
6138}
6139
6140fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
6141    let mut candidates = Vec::new();
6142    for candidate in ctx
6143        .visibility
6144        .visible_identifier_candidates(ctx.file, name)
6145        .filter(|candidate| {
6146            candidate.is_class()
6147                || ctx
6148                    .analyzer
6149                    .type_alias_provider()
6150                    .is_some_and(|provider| provider.is_type_alias(candidate))
6151        })
6152    {
6153        if !candidates
6154            .iter()
6155            .any(|existing| same_logical_symbol(existing, candidate))
6156        {
6157            candidates.push(candidate.clone());
6158        }
6159    }
6160    candidates
6161}
6162
6163/// Recover a direct type-alias argument of `static_cast` when parser recovery
6164/// misclassifies a namespace alias as a local declaration. The indexed scope
6165/// and exact alias identity are required so a same-spelled alias in another
6166/// namespace remains excluded.
6167fn target_guided_static_cast_alias_type_descriptor<'tree>(
6168    node: Node<'tree>,
6169    ctx: &ScanCtx<'_>,
6170) -> Option<Node<'tree>> {
6171    if node.kind() != "type_descriptor" {
6172        return None;
6173    }
6174    let argument_list = ctx.ancestry.parent(node).filter(|parent| {
6175        parent.kind() == "template_argument_list"
6176            && parent.named_child_count() == 1
6177            && parent.named_child(0) == Some(node)
6178    })?;
6179    let template = ctx.ancestry.parent(argument_list).filter(|parent| {
6180        parent.kind() == "template_function"
6181            && parent.child_by_field_name("arguments") == Some(argument_list)
6182    })?;
6183    let name = template.child_by_field_name("name")?;
6184    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
6185        return None;
6186    }
6187    let target = &ctx.spec.target;
6188    if node_text(node, ctx.source) != target.identifier()
6189        || !ctx
6190            .analyzer
6191            .type_alias_provider()
6192            .is_some_and(|provider| provider.is_type_alias(target))
6193        || !ctx.visibility.is_physically_visible(ctx.file, target)
6194        || !ctx.visibility.external_type_candidate_visible_in_context(
6195            &ctx.analyzer,
6196            ctx.file,
6197            target,
6198            node,
6199        )
6200    {
6201        return None;
6202    }
6203
6204    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
6205    let target_scope = canonical_cpp_scope_components(target);
6206    let name_components = [target.identifier().to_string()];
6207    if !lexical_component_tiers(&name_components, false, &indexed_scope)
6208        .any(|components| components == target_scope)
6209    {
6210        return None;
6211    }
6212
6213    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
6214    if !candidates
6215        .iter()
6216        .any(|candidate| same_visible_symbol(candidate, target))
6217    {
6218        return None;
6219    }
6220    if candidates.iter().any(|candidate| {
6221        !same_visible_symbol(candidate, target)
6222            && lexical_component_tiers(&name_components, false, &indexed_scope)
6223                .any(|components| components == canonical_cpp_scope_components(candidate))
6224    }) {
6225        return None;
6226    }
6227    Some(node)
6228}
6229
6230fn indexed_scope_matches_target_name(
6231    indexed_scope: &[String],
6232    components: &[String],
6233    global: bool,
6234    target: &CodeUnit,
6235) -> bool {
6236    let target_name = cpp_name_for(target);
6237    lexical_component_tiers(components, global, indexed_scope)
6238        .any(|qualified| qualified.join("::") == target_name)
6239}
6240
6241fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
6242    // A global scope or one surviving name alone cannot distinguish a lost
6243    // namespace from a real global/unrelated reference. Require an enclosing
6244    // declaration component as well as a namespace component matching the
6245    // target's qualified scope.
6246    if target.package_name().is_empty() || indexed_scope.len() <= 1 {
6247        return false;
6248    }
6249    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6250        brokk_bifrost_core::analyzer::Language::Cpp,
6251        &cpp_name_for(target),
6252    );
6253    target_scope.pop();
6254    (1..indexed_scope.len())
6255        .rev()
6256        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
6257}
6258
6259fn indexed_enclosing_lexical_scope(
6260    analyzer: &CppGraphSource<'_>,
6261    file: &ProjectFile,
6262    node: Node<'_>,
6263) -> Option<Vec<String>> {
6264    let range = Range {
6265        start_byte: node.start_byte(),
6266        end_byte: node.end_byte(),
6267        start_line: node.start_position().row,
6268        end_line: node.end_position().row,
6269    };
6270    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6271    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6272        brokk_bifrost_core::analyzer::Language::Cpp,
6273        &cpp_name_for(&enclosing),
6274    );
6275    if !enclosing.is_class() && !enclosing.is_module() {
6276        components.pop();
6277    }
6278    Some(components)
6279}
6280
6281fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6282    if node.kind() != "qualified_identifier" {
6283        return None;
6284    }
6285    let mut stack = vec![node];
6286    while let Some(current) = stack.pop() {
6287        if current.kind() != "qualified_identifier" {
6288            continue;
6289        }
6290        if let Some(scope) = current.child_by_field_name("scope") {
6291            let text = qualified_scope_text(scope, ctx.source);
6292            if name_mentions(&text, &ctx.spec.member_name) {
6293                return Some(scope);
6294            }
6295        }
6296        let mut cursor = current.walk();
6297        for child in current.named_children(&mut cursor) {
6298            if child.kind() == "qualified_identifier" {
6299                stack.push(child);
6300            }
6301        }
6302    }
6303    None
6304}
6305
6306fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6307    let mut parts = vec![node_text(scope, source).to_string()];
6308    let mut current = scope.parent();
6309    while let Some(qualified) = current {
6310        let Some(parent) = qualified.parent() else {
6311            break;
6312        };
6313        if parent.kind() != "qualified_identifier"
6314            || parent.child_by_field_name("name") != Some(qualified)
6315        {
6316            break;
6317        }
6318        if let Some(outer_scope) = parent.child_by_field_name("scope") {
6319            parts.push(node_text(outer_scope, source).to_string());
6320        }
6321        current = Some(parent);
6322    }
6323    parts.reverse();
6324    parts.join("::")
6325}
6326
6327fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6328    if node.kind() == "using_declaration" {
6329        maybe_record_using_callable_hit(node, ctx);
6330        return;
6331    }
6332    if has_ancestor_kind(node, "using_declaration") {
6333        return;
6334    }
6335    if node.kind() == "function_definition" {
6336        return;
6337    }
6338    if !matches!(
6339        node.kind(),
6340        "call_expression"
6341            | "new_expression"
6342            | "compound_literal_expression"
6343            | "declaration"
6344            | "field_initializer"
6345    ) {
6346        return;
6347    }
6348    let Some(owner) = ctx.spec.owner.as_ref() else {
6349        return;
6350    };
6351    if node.kind() == "field_initializer" {
6352        if !field_initializer_constructs_target(node, ctx, owner)
6353            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6354        {
6355            return;
6356        }
6357        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6358            match ctx
6359                .visibility
6360                .call_arity_evidence(ctx.file, node, ctx.source)
6361                .accepts(expected)
6362            {
6363                Some(true) => {}
6364                Some(false) => return,
6365                None => {
6366                    push_unproven_hit(node, ctx);
6367                    return;
6368                }
6369            }
6370        }
6371        match constructor_overload_selection(node, ctx) {
6372            ConstructorOverloadSelection::Target => push_hit(node, ctx),
6373            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6374            ConstructorOverloadSelection::OtherOverload => {}
6375        }
6376        return;
6377    }
6378    if node.kind() == "declaration" {
6379        if declaration_is_object_construction_candidate(node, ctx)
6380            && declaration_mentions_type(node, ctx, owner)
6381            && ctx
6382                .spec
6383                .callable_arity_at(node.start_byte())
6384                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6385        {
6386            // The spelling names the owner in some configuration. The type
6387            // node's structured resolution applies this declaration's guards
6388            // to conditional alias arms, so an arm naming another class is not
6389            // this constructor's site (#6902).
6390            let structured = node.child_by_field_name("type").map(|type_node| {
6391                resolve_type_node_lexically_for_target(
6392                    type_node,
6393                    &ctx.analyzer,
6394                    ctx.visibility,
6395                    &ctx.ordinary_type_imports,
6396                    ctx.file,
6397                    ctx.source,
6398                    owner,
6399                    Some(&ctx.lexical_scope_cache),
6400                    ctx.recovered_sentinel_scope(type_node).as_deref(),
6401                )
6402            });
6403            match structured {
6404                Some(LexicalTypeResolution::Resolved {
6405                    unit, candidates, ..
6406                }) if !same_visible_symbol(&unit, owner)
6407                    && !candidates
6408                        .iter()
6409                        .any(|candidate| same_visible_symbol(candidate, owner)) =>
6410                {
6411                    return;
6412                }
6413                Some(LexicalTypeResolution::Ambiguous) => {
6414                    push_unproven_hit(node, ctx);
6415                    return;
6416                }
6417                Some(LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing)
6418                | None => {}
6419            }
6420            match constructor_overload_selection(node, ctx) {
6421                ConstructorOverloadSelection::Target => push_hit(node, ctx),
6422                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6423                ConstructorOverloadSelection::OtherOverload => {}
6424            }
6425        }
6426        return;
6427    }
6428    let Some(type_node) = constructor_type_node(node) else {
6429        return;
6430    };
6431    let hit_node = function_terminal_node(type_node);
6432    let text = node_text(type_node, ctx.source);
6433    if !name_mentions(text, &ctx.spec.member_name) {
6434        return;
6435    }
6436    *ctx.raw_match_count += 1;
6437    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6438        match ctx
6439            .visibility
6440            .call_arity_evidence(ctx.file, node, ctx.source)
6441            .accepts(expected)
6442        {
6443            Some(true) => {}
6444            Some(false) => return,
6445            None => {
6446                push_unproven_hit(hit_node, ctx);
6447                return;
6448            }
6449        }
6450    }
6451    match constructor_overload_selection(node, ctx) {
6452        ConstructorOverloadSelection::Target => {}
6453        ConstructorOverloadSelection::Ambiguous => {
6454            push_unproven_hit(hit_node, ctx);
6455            return;
6456        }
6457        ConstructorOverloadSelection::OtherOverload => return,
6458    }
6459    let structured_resolution = resolve_type_node_lexically_for_target(
6460        type_node,
6461        &ctx.analyzer,
6462        ctx.visibility,
6463        &ctx.ordinary_type_imports,
6464        ctx.file,
6465        ctx.source,
6466        owner,
6467        Some(&ctx.lexical_scope_cache),
6468        ctx.recovered_sentinel_scope(type_node).as_deref(),
6469    );
6470    let structurally_resolves = matches!(
6471        &structured_resolution,
6472        LexicalTypeResolution::Resolved {
6473            unit, candidates, ..
6474        } if same_visible_symbol(unit, owner)
6475            || candidates
6476                .iter()
6477                .any(|candidate| same_visible_symbol(candidate, owner))
6478    );
6479    if structurally_resolves
6480        || matches!(structured_resolution, LexicalTypeResolution::Missing)
6481            && ctx
6482                .visibility
6483                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6484    {
6485        push_hit(hit_node, ctx);
6486    } else {
6487        push_unproven_hit(hit_node, ctx);
6488    }
6489}
6490
6491/// What the overload filter says about a construction site relative to the
6492/// constructor being scanned.
6493enum ConstructorOverloadSelection {
6494    /// The arguments leave the scan target as the only viable constructor, or
6495    /// the filter has no evidence to apply here. Either way nothing about the
6496    /// arguments argues against the site, so the rest of the scan decides it.
6497    Target,
6498    /// More than one of the owner's constructors stays viable, so the site
6499    /// cannot be proven to belong to the target.
6500    Ambiguous,
6501    /// The arguments select a sibling constructor and not the target.
6502    OtherOverload,
6503}
6504
6505/// Which of the owner's constructors the arguments at a construction site
6506/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
6507/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
6508/// `T var(args)` declaration.
6509///
6510/// This is the inverse reading of the forward direction's overload choice. The
6511/// candidate set is the owner's arity-compatible constructors, the arguments are
6512/// typed by the same `expression_arg_type` the method and free-function scan
6513/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
6514/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
6515/// arity alone made a scan of one overload claim every same-arity sibling's call
6516/// (#2908).
6517fn constructor_overload_selection(
6518    node: Node<'_>,
6519    ctx: &ScanCtx<'_>,
6520) -> ConstructorOverloadSelection {
6521    let Some(owner) = ctx.spec.owner.as_ref() else {
6522        return ConstructorOverloadSelection::Target;
6523    };
6524    if ctx.spec.param_types.is_none() {
6525        return ConstructorOverloadSelection::Target;
6526    }
6527    // A `T var(args)` declaration keeps its arguments under the declarator or the
6528    // init declarator's value; every other shape spells an argument list that the
6529    // shared call helpers find, and its argument count needs the macro-aware
6530    // arity evidence.
6531    let (arity, arg_types) = if node.kind() == "declaration" {
6532        match declaration_constructor_initializer(node) {
6533            DeclarationConstructorInitializer::Arguments(arguments) => (
6534                argument_children(arguments).count(),
6535                argument_list_types(arguments, ctx),
6536            ),
6537            DeclarationConstructorInitializer::Expression(value) => {
6538                (1, vec![expression_arg_type(value, ctx)])
6539            }
6540            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6541        }
6542    } else {
6543        let Some(arity) = ctx
6544            .visibility
6545            .call_arity_evidence(ctx.file, node, ctx.source)
6546            .exact()
6547        else {
6548            return ConstructorOverloadSelection::Target;
6549        };
6550        (arity, call_argument_types(node, ctx))
6551    };
6552    let mut candidates = ctx
6553        .visibility
6554        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6555        .into_iter()
6556        .filter(|unit| unit.is_function())
6557        .cloned()
6558        .collect::<Vec<_>>();
6559    candidates.retain(|unit| {
6560        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6561    });
6562    if !candidates
6563        .iter()
6564        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6565    {
6566        return ConstructorOverloadSelection::Target;
6567    }
6568    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6569        candidates,
6570        &arg_types,
6571        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6572        &|candidate| {
6573            ctx.visibility
6574                .callable_is_template_declaration(&ctx.analyzer, candidate)
6575        },
6576        &|name| ctx.visibility.resolve_type(ctx.file, name),
6577        &|left, right| same_visible_symbol(left, right),
6578    );
6579    if !filtered
6580        .iter()
6581        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6582    {
6583        return ConstructorOverloadSelection::OtherOverload;
6584    }
6585    // A header declaration and its out-of-line body are one constructor even
6586    // when their persisted signature strings spell a parameter differently, so
6587    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
6588    if filtered.iter().all(|candidate| {
6589        ctx.visibility
6590            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6591    }) {
6592        ConstructorOverloadSelection::Target
6593    } else {
6594        ConstructorOverloadSelection::Ambiguous
6595    }
6596}
6597
6598fn unqualified_base_initializer_constructs_target(
6599    node: Node<'_>,
6600    ctx: &ScanCtx<'_>,
6601    target_owner: &CodeUnit,
6602) -> bool {
6603    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6604        return false;
6605    }
6606    let Some(name) = node
6607        .child_by_field_name("name")
6608        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6609    else {
6610        return false;
6611    };
6612    if node_text(name, ctx.source) != ctx.spec.member_name {
6613        return false;
6614    }
6615    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6616        return false;
6617    };
6618    let inherited = ctx.visibility.inherited_injected_class_owner(
6619        &ctx.analyzer,
6620        ctx.file,
6621        &enclosing_owner,
6622        &ctx.spec.member_name,
6623    );
6624    inherited.is_some_and(|owner| {
6625        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6626    })
6627}
6628
6629fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6630    if node.kind() == "function_definition" {
6631        maybe_record_free_function_definition_hit(node, ctx);
6632        return;
6633    }
6634    if node.kind() == "function_declarator" {
6635        maybe_record_recovered_error_free_function_call(node, ctx);
6636        return;
6637    }
6638    if node.kind() == "identifier" {
6639        maybe_record_free_function_value_reference(node, ctx);
6640        return;
6641    }
6642    if node.kind() != "call_expression" {
6643        return;
6644    }
6645    let Some(function) = node
6646        .child_by_field_name("function")
6647        .or_else(|| node.named_child(0))
6648    else {
6649        return;
6650    };
6651    let text = node_text(function, ctx.source);
6652    if !name_matches_callable(text, &ctx.spec.member_name) {
6653        return;
6654    }
6655    *ctx.raw_match_count += 1;
6656    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6657        match ctx
6658            .visibility
6659            .call_arity_evidence(ctx.file, node, ctx.source)
6660            .accepts(expected)
6661        {
6662            Some(true) => {}
6663            Some(false) => return,
6664            None => {
6665                // The argument count is unknown after macro expansion. It still
6666                // cannot select a *different* target when the bare name binds
6667                // to exactly one visible callable, so let the bare-call
6668                // resolution below prove that site; every other shape stays
6669                // unproven (#1811, the scan side of the same over-conservatism
6670                // that made the forward answer discard its lone candidate).
6671                if !bare_name_binds_only_target(node, function, text, ctx) {
6672                    push_unproven_hit(function_terminal_node(function), ctx);
6673                    return;
6674                }
6675            }
6676        }
6677    }
6678    if matches!(function.kind(), "identifier" | "template_function") {
6679        let terminal = function_terminal_node(function);
6680        let name = node_text(terminal, ctx.source);
6681        if ctx.local_shadows.is_shadowed(name) {
6682            return;
6683        }
6684        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6685            && !matches!(
6686                resolve_declaring_member_owner(
6687                    &ctx.analyzer,
6688                    ctx.visibility,
6689                    ctx.file,
6690                    &enclosing_owner,
6691                    name,
6692                ),
6693                EnclosingMemberOwnerResolution::Missing
6694            )
6695        {
6696            return;
6697        }
6698        match resolve_bare_call_target(
6699            node,
6700            function,
6701            &ctx.analyzer,
6702            ctx.visibility,
6703            &ctx.ordinary_type_imports,
6704            ctx.file,
6705            ctx.source,
6706        ) {
6707            BareCallTargetResolution::FreeFunctions(units)
6708                if units
6709                    .iter()
6710                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6711            {
6712                if free_function_call_may_target(node, text, ctx) {
6713                    let recursive = enclosing_context(terminal, ctx)
6714                        .enclosing
6715                        .as_ref()
6716                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6717                    if recursive {
6718                        push_recursive_reference_hit(terminal, ctx);
6719                    } else {
6720                        push_hit(terminal, ctx);
6721                    }
6722                }
6723            }
6724            BareCallTargetResolution::UnprovenFreeFunctions(units)
6725                if units
6726                    .iter()
6727                    .any(|unit| free_function_target_matches(unit, ctx)) =>
6728            {
6729                push_unproven_hit(terminal, ctx);
6730            }
6731            BareCallTargetResolution::FreeFunctions(_)
6732            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6733            | BareCallTargetResolution::Type(_)
6734            | BareCallTargetResolution::CallableShadow => {}
6735            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6736                push_unproven_hit(terminal, ctx);
6737            }
6738        }
6739        return;
6740    }
6741    if !free_function_call_may_target(node, text, ctx) {
6742        return;
6743    }
6744    if ctx.visibility.contains_named_symbol(
6745        ctx.file,
6746        text,
6747        TargetKind::FreeFunction,
6748        &ctx.spec.target,
6749    ) {
6750        push_hit(function_terminal_node(function), ctx);
6751    } else if ctx.visibility.resolve_known_non_target(
6752        ctx.file,
6753        text,
6754        TargetKind::FreeFunction,
6755        &ctx.spec.target,
6756    ) {
6757        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6758        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6759    } else {
6760        push_unproven_hit(function_terminal_node(function), ctx);
6761    }
6762}
6763
6764fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6765    same_visible_symbol(unit, &ctx.spec.target)
6766        || ctx
6767            .visibility
6768            .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6769            && cpp_callable_definitions_share_identity_evidence_with_visibility(
6770                &ctx.analyzer,
6771                ctx.visibility,
6772                unit,
6773                &ctx.spec.target,
6774            )
6775}
6776
6777/// Recover a bare call whose adjacent object-like macro arguments made
6778/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6779///
6780/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6781/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6782/// real block-scope function declaration remains under a `declaration`, so the
6783/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6784/// out of declaration syntax. The malformed parameter list cannot establish
6785/// arity; publish a proven hit only when ordinary visibility leaves one
6786/// callable identity for the bare name.
6787fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6788    // The macro-declaration grammar keeps the same call as a bogus
6789    // `function_definition` whose damaged declarator ends at the block
6790    // literal's `(`: its parameter list is the call's argument prefix, so
6791    // unlike the ERROR shape it carries countable arity evidence.
6792    let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6793    if !block_call_envelope
6794        && (ctx
6795            .ancestry
6796            .parent(node)
6797            .is_none_or(|parent| parent.kind() != "ERROR")
6798            || !has_ancestor_kind(node, "compound_statement"))
6799    {
6800        return;
6801    }
6802    let mut recovered_functions = Vec::new();
6803    if let Some(function) = node
6804        .child_by_field_name("declarator")
6805        .filter(|function| function.kind() == "identifier")
6806    {
6807        recovered_functions.push(function);
6808    }
6809    if let Some(parameters) = node.child_by_field_name("parameters") {
6810        let mut cursor = parameters.walk();
6811        for parameter in parameters
6812            .named_children(&mut cursor)
6813            .filter(|child| child.kind() == "parameter_declaration")
6814        {
6815            if parameter
6816                .child_by_field_name("declarator")
6817                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6818                && let Some(function) = parameter
6819                    .child_by_field_name("type")
6820                    .filter(|function| function.kind() == "type_identifier")
6821            {
6822                recovered_functions.push(function);
6823            }
6824        }
6825    }
6826
6827    for function in recovered_functions {
6828        let name = node_text(function, ctx.source);
6829        if !name_matches_callable(name, &ctx.spec.member_name)
6830            || ctx.local_shadows.is_shadowed(name)
6831        {
6832            continue;
6833        }
6834        *ctx.raw_match_count += 1;
6835        if block_call_envelope
6836            && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6837            && ctx
6838                .visibility
6839                .call_arity_evidence(ctx.file, node, ctx.source)
6840                .accepts(expected)
6841                == Some(false)
6842        {
6843            continue;
6844        }
6845        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6846            push_hit(function, ctx);
6847            continue;
6848        }
6849        let mut candidates =
6850            ctx.visibility
6851                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6852        candidates.retain(|candidate| {
6853            ctx.visibility.declaration_visible_at(
6854                &ctx.analyzer,
6855                ctx.file,
6856                candidate,
6857                function.start_byte(),
6858            )
6859        });
6860        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6861        if candidates
6862            .iter()
6863            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6864        {
6865            push_unproven_hit(function, ctx);
6866        }
6867    }
6868}
6869
6870/// Whether the bare name at `call` binds to exactly one visible callable, and
6871/// that callable is the scan target.
6872///
6873/// This is the scan-side reading of the #1811 rule: with one name binding there
6874/// is nothing an unknown argument count could select instead, so the site is a
6875/// proven reference rather than an unproven one. Only bare identifiers qualify;
6876/// a member or qualified call reaches its target through a receiver this cannot
6877/// judge.
6878fn bare_name_binds_only_target(
6879    call: Node<'_>,
6880    function: Node<'_>,
6881    text: &str,
6882    ctx: &ScanCtx<'_>,
6883) -> bool {
6884    if !matches!(function.kind(), "identifier" | "template_function") {
6885        return false;
6886    }
6887    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6888}
6889
6890fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6891    let mut candidates = ctx
6892        .visibility
6893        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6894    candidates.retain(|candidate| {
6895        ctx.visibility
6896            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6897    });
6898    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6899    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6900}
6901
6902fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6903    if ctx.spec.param_types.is_none() {
6904        return true;
6905    }
6906    let mut candidates = ctx
6907        .visibility
6908        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6909    candidates.retain(|candidate| {
6910        ctx.visibility
6911            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6912    });
6913    callable_candidates_may_match_target(call, candidates, ctx)
6914}
6915
6916fn callable_candidates_may_match_target(
6917    call: Node<'_>,
6918    mut candidates: Vec<CodeUnit>,
6919    ctx: &ScanCtx<'_>,
6920) -> bool {
6921    let Some(arity) = ctx
6922        .visibility
6923        .call_arity_evidence(ctx.file, call, ctx.source)
6924        .exact()
6925    else {
6926        return true;
6927    };
6928    candidates.retain(|unit| {
6929        cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6930    });
6931    if candidates.is_empty()
6932        || !candidates
6933            .iter()
6934            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6935    {
6936        return true;
6937    }
6938    let arg_types = call_argument_types(call, ctx);
6939    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6940        candidates,
6941        &arg_types,
6942        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6943        &|candidate| {
6944            ctx.visibility
6945                .callable_is_template_declaration(&ctx.analyzer, candidate)
6946        },
6947        &|name| ctx.visibility.resolve_type(ctx.file, name),
6948        &|left, right| same_visible_symbol(left, right),
6949    );
6950    filtered
6951        .iter()
6952        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6953}
6954
6955/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6956/// in order.
6957///
6958/// A `None` entry is an argument the index cannot type; one of them makes
6959/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6960fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6961    argument_children(arguments)
6962        .map(|arg| expression_arg_type(arg, ctx))
6963        .collect()
6964}
6965
6966fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6967    call_arguments_node(call)
6968        .map(|arguments| argument_list_types(arguments, ctx))
6969        .unwrap_or_default()
6970}
6971
6972fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6973    match node.kind() {
6974        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6975        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6976            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6977            literal
6978        }),
6979        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6980        // `T v(x);` is the vexing parse: the grammar reads it as a function
6981        // declaration whose one parameter is `x` spelled as a type. A parameter
6982        // declaration that is nothing but a type identifier is that reading, and
6983        // the identifier it holds is the constructor argument.
6984        "parameter_declaration" => {
6985            let declared = node.child_by_field_name("type")?;
6986            (node.child_by_field_name("declarator").is_none()
6987                && declared.kind() == "type_identifier")
6988                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6989                .flatten()
6990        }
6991        // `this->m_params` names the same data member as the bare `m_params`.
6992        "field_expression" => {
6993            let receiver = node
6994                .child_by_field_name("argument")
6995                .or_else(|| node.named_child(0))?;
6996            let field = node.child_by_field_name("field")?;
6997            (receiver.kind() == "this")
6998                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6999                .flatten()
7000        }
7001        "parenthesized_expression" => node
7002            .child_by_field_name("argument")
7003            .or_else(|| node.named_child(0))
7004            .and_then(|inner| expression_arg_type(inner, ctx)),
7005        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
7006        // forward (#2552).
7007        "call_expression" => {
7008            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
7009            expression_arg_type(forwarded, ctx)
7010        }
7011        "pointer_expression" => {
7012            let delta = match node.child_by_field_name("operator")?.kind() {
7013                "&" => 1,
7014                "*" => -1,
7015                _ => return None,
7016            };
7017            let inner = node
7018                .child_by_field_name("argument")
7019                .or_else(|| node.named_child(0))?;
7020            let mut arg_type = expression_arg_type(inner, ctx)?;
7021            arg_type.indirection += delta;
7022            Some(arg_type)
7023        }
7024        _ => None,
7025    }
7026}
7027
7028/// The type of a bare value name used as a call argument: the local or
7029/// parameter binding when one is in scope, otherwise the enclosing class's data
7030/// member of that name.
7031///
7032/// Unqualified lookup reaches a local before a data member, so a local of the
7033/// same name ends the search rather than falling through to the field. This
7034/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
7035/// the two directions agreeing about which overload a member-field argument
7036/// selects (#2894).
7037fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
7038    match ctx.bindings.resolve_symbol(name) {
7039        SymbolResolution::Precise(bindings) => {
7040            bindings.iter().find_map(CppScanBinding::as_arg_type)
7041        }
7042        SymbolResolution::Ambiguous => None,
7043        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
7044            .then(|| enclosing_member_field_arg_type(node, name, ctx))
7045            .flatten(),
7046    }
7047}
7048
7049/// The declared type of `name` read as a data member of the class that lexically
7050/// owns `node`.
7051///
7052/// `None` when there is no enclosing class, when the name is not one data member
7053/// of it, or when the member's declared type does not resolve. An unknown
7054/// argument type makes the overload filter keep every candidate, which is the
7055/// honest answer for a field the index cannot type.
7056fn enclosing_member_field_arg_type(
7057    node: Node<'_>,
7058    name: &str,
7059    ctx: &ScanCtx<'_>,
7060) -> Option<CppArgType> {
7061    let owner = enclosing_context(node, ctx).owner?;
7062    let fields = ctx
7063        .visibility
7064        .visible_members_for_owner_name(ctx.file, &owner, name)
7065        .into_iter()
7066        .filter(|unit| unit.is_field())
7067        .collect::<Vec<_>>();
7068    let [field] = fields.as_slice() else {
7069        return None;
7070    };
7071    let (type_name, unit, indirection) =
7072        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
7073    Some(CppArgType {
7074        name: type_name,
7075        unit,
7076        indirection,
7077        pointee_const: false,
7078    })
7079}
7080
7081/// Record a *non-call* reference to a free function used as a value: `&foo`,
7082/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
7083/// `foo()` is recorded by the call_expression arm, and the function's own
7084/// declaration/definition name is not a reference.
7085fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7086    let text = node_text(node, ctx.source);
7087    if !name_matches_callable(text, &ctx.spec.member_name) {
7088        return;
7089    }
7090    if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
7091        *ctx.raw_match_count += 1;
7092        match kind {
7093            RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
7094            RecoveredCallableDeclarationKind::Definition => {
7095                push_recovered_definition_hit(node, ctx)
7096            }
7097        }
7098        return;
7099    }
7100    if is_declaration_name(node) {
7101        maybe_record_free_function_declaration_reference(node, ctx);
7102        return;
7103    }
7104    if is_call_callee_node(node) {
7105        return;
7106    }
7107    *ctx.raw_match_count += 1;
7108    if ctx.visibility.contains_named_symbol(
7109        ctx.file,
7110        text,
7111        TargetKind::FreeFunction,
7112        &ctx.spec.target,
7113    ) {
7114        push_hit(node, ctx);
7115    } else if ctx.visibility.resolve_known_non_target(
7116        ctx.file,
7117        text,
7118        TargetKind::FreeFunction,
7119        &ctx.spec.target,
7120    ) {
7121        // A qualified reference proven to a different namespace is not a match.
7122    } else {
7123        push_unproven_hit(node, ctx);
7124    }
7125}
7126
7127#[derive(Clone, Copy)]
7128enum RecoveredCallableDeclarationKind {
7129    Declaration,
7130    Definition,
7131}
7132
7133/// Recover the role of a callable name whose declaration was split by an
7134/// ERROR node. An indexed declaration range proves identity when available;
7135/// for a top-level C callable that recovery failed to range-index, same-file
7136/// name identity is sufficient because C has no function overloading. The CST
7137/// shape distinguishes a prototype from a definition. C++ parsing of
7138/// an unknown C return type can put the name under an ERROR child of the
7139/// function declarator, while call-shaped recovery puts that declarator under
7140/// an ERROR whose parent is a compound statement; only the former is admitted.
7141fn recovered_callable_declaration_kind(
7142    node: Node<'_>,
7143    ctx: &ScanCtx<'_>,
7144) -> Option<RecoveredCallableDeclarationKind> {
7145    if !matches!(node.kind(), "identifier" | "field_identifier") {
7146        return None;
7147    }
7148    let target_identity_proven =
7149        ctx.target_declaration_ranges.iter().any(|range| {
7150            range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7151        }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
7152            && ctx.spec.target.is_function()
7153            && ctx.spec.target.source() == ctx.file);
7154    let mut current = ctx.ancestry.parent(node);
7155    while let Some(parent) = current {
7156        if parent.is_error() {
7157            // C files with an unknown return type can retain the real
7158            // callable name as `function_declarator > ERROR`, with the ERROR
7159            // as a named child rather than the declarator field itself.
7160            let error = parent;
7161            let Some(function) = ctx.ancestry.parent(error).filter(|function| {
7162                function.kind() == "function_declarator"
7163                    && function
7164                        .child_by_field_name("parameters")
7165                        .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
7166            }) else {
7167                current = ctx.ancestry.parent(parent);
7168                continue;
7169            };
7170            let container = ctx.ancestry.parent(function)?;
7171            let kind = match container.kind() {
7172                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7173                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7174                _ => return None,
7175            };
7176            return target_identity_proven.then_some(kind);
7177        }
7178        if parent.kind() == "function_declarator" {
7179            let error = ctx
7180                .ancestry
7181                .parent(parent)
7182                .filter(|parent| parent.is_error())?;
7183            let declarator = parent.child_by_field_name("declarator")?;
7184            if declarator.start_byte() > node.start_byte()
7185                || node.end_byte() > declarator.end_byte()
7186            {
7187                return None;
7188            }
7189            let container = ctx.ancestry.parent(error)?;
7190            let kind = match container.kind() {
7191                "declaration" => RecoveredCallableDeclarationKind::Declaration,
7192                "function_definition" => RecoveredCallableDeclarationKind::Definition,
7193                _ => return None,
7194            };
7195            return target_identity_proven.then_some(kind);
7196        }
7197        if parent.is_error() {
7198            current = ctx.ancestry.parent(parent);
7199            continue;
7200        }
7201        if matches!(
7202            parent.kind(),
7203            "translation_unit" | "function_definition" | "compound_statement"
7204        ) {
7205            return None;
7206        }
7207        current = ctx.ancestry.parent(parent);
7208    }
7209    None
7210}
7211
7212fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7213    if !ctx.spec.callable_has_definition_body {
7214        return;
7215    }
7216    let text = node_text(node, ctx.source);
7217    if !name_matches_callable(text, &ctx.spec.member_name) {
7218        return;
7219    }
7220    let mut declaration = ctx.ancestry.parent(node);
7221    while let Some(candidate) = declaration {
7222        if candidate.kind() == "function_definition" {
7223            return;
7224        }
7225        if candidate.kind() == "declaration" {
7226            break;
7227        }
7228        declaration = ctx.ancestry.parent(candidate);
7229    }
7230    if declaration.is_none() {
7231        return;
7232    }
7233    let linked_declaration = ctx
7234        .visibility
7235        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
7236        .into_iter()
7237        .find(|candidate| {
7238            candidate.source() == ctx.file
7239                && candidate.is_function()
7240                && (!ctx.target_group.contains(candidate)
7241                    || candidate.source() == ctx.spec.target.source())
7242                && ctx.analyzer.ranges(candidate).iter().any(|range| {
7243                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
7244                })
7245                && (candidate.source() == ctx.spec.target.source()
7246                    && candidate.fq_name() == ctx.spec.target.fq_name()
7247                    && candidate.signature() == ctx.spec.target.signature()
7248                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
7249                        &ctx.analyzer,
7250                        ctx.visibility,
7251                        candidate,
7252                        &ctx.spec.target,
7253                    ))
7254                && callable_arity_matches_target(candidate, node, ctx)
7255        });
7256    if linked_declaration.is_none() {
7257        return;
7258    }
7259    *ctx.raw_match_count += 1;
7260    push_declaration_reference_hit(node, ctx);
7261}
7262
7263fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7264    let Some(function) = function_definition_name_node(node) else {
7265        return;
7266    };
7267    let text = node_text(function, ctx.source);
7268    if !name_matches_callable(text, &ctx.spec.member_name) {
7269        return;
7270    }
7271    *ctx.raw_match_count += 1;
7272    if !function_definition_signature_matches_target(node, ctx) {
7273        return;
7274    }
7275    match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
7276        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7277        DefinitionNameTargetResolution::KnownNonTarget => {
7278            // A definition in another explicit namespace is a proven non-match.
7279        }
7280        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7281    }
7282}
7283
7284fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7285    if node.kind() == "preproc_arg" {
7286        maybe_record_function_macro_replacement_method_hits(node, ctx);
7287        return;
7288    }
7289    if node.kind() == "using_declaration" {
7290        maybe_record_using_callable_hit(node, ctx);
7291        return;
7292    }
7293    if has_ancestor_kind(node, "using_declaration") {
7294        return;
7295    }
7296    if node.kind() == "function_definition" {
7297        maybe_record_method_definition_hit(node, ctx);
7298        return;
7299    }
7300    if is_declaration_name(node) {
7301        return;
7302    }
7303    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7304        maybe_record_qualified_method_value_hit(node, member, ctx);
7305        return;
7306    }
7307    if let Some(value) = qualified_callable_value(node) {
7308        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7309        return;
7310    }
7311    if let Some(call) = recovered_relational_template_member_call(node) {
7312        maybe_record_recovered_relational_template_method_hit(call, ctx);
7313        return;
7314    }
7315    if node.kind() != "call_expression" {
7316        return;
7317    }
7318    if let Some((receiver, operator)) = explicit_operator_call(node) {
7319        let text = node_text(operator, ctx.source);
7320        if !name_matches_callable(text, &ctx.spec.member_name) {
7321            return;
7322        }
7323        *ctx.raw_match_count += 1;
7324        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7325            match ctx
7326                .visibility
7327                .call_arity_evidence(ctx.file, node, ctx.source)
7328                .accepts(expected)
7329            {
7330                Some(true) => {}
7331                Some(false) => return,
7332                None => {
7333                    push_unproven_hit(operator, ctx);
7334                    return;
7335                }
7336            }
7337        }
7338        let arity = ctx
7339            .visibility
7340            .call_arity_evidence(ctx.file, node, ctx.source)
7341            .exact();
7342        record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7343        return;
7344    }
7345    let Some(function) = node
7346        .child_by_field_name("function")
7347        .or_else(|| node.named_child(0))
7348    else {
7349        return;
7350    };
7351    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7352        return;
7353    }
7354    if function.kind() == "identifier"
7355        && ctx
7356            .local_shadows
7357            .is_shadowed(node_text(function, ctx.source))
7358    {
7359        return;
7360    }
7361    *ctx.raw_match_count += 1;
7362    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7363        match ctx
7364            .visibility
7365            .call_arity_evidence(ctx.file, node, ctx.source)
7366            .accepts(expected)
7367        {
7368            Some(true) => {}
7369            Some(false) => return,
7370            None => {
7371                push_unproven_hit(function_terminal_node(function), ctx);
7372                return;
7373            }
7374        }
7375    }
7376    if !method_call_may_target(node, ctx) {
7377        return;
7378    }
7379    if is_structurally_qualified(function) {
7380        match qualified_owner_resolution(function, ctx) {
7381            QualifiedOwnerResolution::Target => {
7382                push_hit(function_terminal_node(function), ctx);
7383            }
7384            QualifiedOwnerResolution::NonTarget => {}
7385            QualifiedOwnerResolution::Unresolved => {
7386                push_unproven_hit(function_terminal_node(function), ctx);
7387            }
7388        }
7389        return;
7390    }
7391    match call_function_target_resolution(function, ctx) {
7392        MethodReceiverTargetResolution::Target
7393            if call_function_has_direct_self_receiver(
7394                function,
7395                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7396            ) =>
7397        {
7398            push_self_receiver_hit(function_terminal_node(function), ctx);
7399        }
7400        MethodReceiverTargetResolution::Target => {
7401            push_hit(function_terminal_node(function), ctx);
7402        }
7403        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7404        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
7405        // the target member declared on a base is a genuine external usage of that inherited
7406        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
7407        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
7408        // arm because `same_owner_context` also accepts this inherited case.
7409        MethodReceiverTargetResolution::Missing
7410            if inherited_target_owner_context(function, ctx) =>
7411        {
7412            push_hit(function_terminal_node(function), ctx);
7413        }
7414        MethodReceiverTargetResolution::Missing
7415            if (matches!(function.kind(), "identifier" | "template_function")
7416                || call_function_has_direct_self_receiver(
7417                    function,
7418                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
7419                ))
7420                && (same_owner_context(function, ctx)
7421                    || out_of_line_target_owner_context(function, ctx)) =>
7422        {
7423            push_self_receiver_hit(function_terminal_node(function), ctx);
7424        }
7425        MethodReceiverTargetResolution::Missing
7426            if function.kind() == "identifier"
7427                && resolves_to_lexical_free_function(function, ctx) =>
7428        {
7429            // A visible namespace/free function is a proven negative once the
7430            // enclosing structured owner and its hierarchy contain no such member.
7431        }
7432        MethodReceiverTargetResolution::Missing
7433            if !receiver_has_known_non_target(function, ctx)
7434                && !known_non_target_owner_context(function, ctx) =>
7435        {
7436            push_unproven_hit(function_terminal_node(function), ctx);
7437        }
7438        MethodReceiverTargetResolution::Missing => {}
7439    }
7440}
7441
7442/// Report member calls written inside a function-like macro's replacement.
7443///
7444/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
7445/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
7446/// it contains (#2549). The resolver's shared sentinel parse recovers them as
7447/// structure, and every hit is reported at the bytes the member spells inside
7448/// that token.
7449fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7450    let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7451        parent.kind() == "preproc_function_def"
7452            && parent
7453                .child_by_field_name("value")
7454                .is_some_and(|value| same_node(value, node))
7455    }) else {
7456        return;
7457    };
7458    let Some(body) = ctx
7459        .visibility
7460        .function_macro_replacement_body(ctx.file, definition, ctx.source)
7461    else {
7462        return;
7463    };
7464    let Some(statements) = body.statements() else {
7465        return;
7466    };
7467    // Collect the candidate calls before typing anything: a replacement that
7468    // never names the queried member must not pay for its declarations.
7469    let mut candidates = Vec::new();
7470    let mut stack = vec![statements];
7471    while let Some(current) = stack.pop() {
7472        push_named_children_reversed(current, &mut stack);
7473        if current.kind() != "call_expression" {
7474            continue;
7475        }
7476        let Some(function) = current
7477            .child_by_field_name("function")
7478            .filter(|function| function.kind() == "field_expression")
7479        else {
7480            continue;
7481        };
7482        let Some(member) = function.child_by_field_name("field") else {
7483            continue;
7484        };
7485        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7486            continue;
7487        }
7488        let Some(receiver) = function
7489            .child_by_field_name("argument")
7490            .or_else(|| function.child_by_field_name("object"))
7491            .or_else(|| function.named_child(0))
7492        else {
7493            continue;
7494        };
7495        candidates.push((current, member, receiver));
7496    }
7497    if candidates.is_empty() {
7498        return;
7499    }
7500    let Some(replacement_start) =
7501        function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7502    else {
7503        return;
7504    };
7505    let locals = macro_replacement_local_receivers(statements, &body, ctx);
7506    for (current, member, receiver) in candidates {
7507        if *ctx.limit_exceeded {
7508            return;
7509        }
7510        let range = body.file_range(member, replacement_start);
7511        debug_assert_eq!(
7512            ctx.source.get(range.clone()),
7513            Some(node_text(member, &body.source)),
7514            "macro replacement member range must spell the member name"
7515        );
7516        *ctx.raw_match_count += 1;
7517        // The replacement's own macro environment is the one at the definition,
7518        // not at the sentinel offsets the recovered call carries.
7519        let arity_evidence = ctx.visibility.call_arity_evidence_at(
7520            ctx.file,
7521            current,
7522            &body.source,
7523            definition.start_byte(),
7524        );
7525        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7526            match arity_evidence.accepts(expected) {
7527                Some(true) => {}
7528                Some(false) => continue,
7529                None => {
7530                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7531                    continue;
7532                }
7533            }
7534        }
7535        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7536        {
7537            MacroReplacementReceiver::Parameter => {
7538                if target_member_is_visible_candidate(ctx) {
7539                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7540                }
7541                continue;
7542            }
7543            MacroReplacementReceiver::Unknown => continue,
7544            MacroReplacementReceiver::Units(units) => {
7545                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7546            }
7547        };
7548        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7549            MethodReceiverTargetResolution::Target => {
7550                push_reference_hit_range(node, range.start, range.end, ctx);
7551            }
7552            MethodReceiverTargetResolution::Missing => {
7553                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7554            }
7555            MethodReceiverTargetResolution::NonTarget
7556            | MethodReceiverTargetResolution::Ambiguous => {}
7557        }
7558    }
7559}
7560
7561enum MacroReplacementReceiver {
7562    /// The receiver is one of the macro's parameters. Its type is supplied by
7563    /// each invocation, so a member call on it can never be proven here.
7564    Parameter,
7565    /// The receiver's declared type resolved to these units.
7566    Units(Vec<CodeUnit>),
7567    /// The receiver has a shape this path does not type.
7568    Unknown,
7569}
7570
7571fn macro_replacement_receiver(
7572    receiver: Node<'_>,
7573    body: &ParsedReplacementBody,
7574    locals: &HashMap<String, Option<CodeUnit>>,
7575    anchor: Node<'_>,
7576    ctx: &ScanCtx<'_>,
7577) -> MacroReplacementReceiver {
7578    let mut current = receiver;
7579    while matches!(
7580        current.kind(),
7581        "parenthesized_expression" | "pointer_expression"
7582    ) {
7583        let Some(inner) = current
7584            .child_by_field_name("argument")
7585            .or_else(|| current.named_child(0))
7586        else {
7587            return MacroReplacementReceiver::Unknown;
7588        };
7589        current = inner;
7590    }
7591    if !matches!(current.kind(), "identifier" | "field_identifier") {
7592        return MacroReplacementReceiver::Unknown;
7593    }
7594    let name = node_text(current, &body.source);
7595    if body.parameters.iter().any(|parameter| parameter == name) {
7596        return MacroReplacementReceiver::Parameter;
7597    }
7598    if let Some(local) = locals.get(name) {
7599        return match local {
7600            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7601            None => MacroReplacementReceiver::Unknown,
7602        };
7603    }
7604    // Neither a parameter nor a replacement-local: an ordinary name the macro
7605    // definition site sees, resolved exactly as the ordinary receiver path
7606    // resolves a bare identifier.
7607    let global_fields = ctx
7608        .visibility
7609        .visible_identifier_candidates(ctx.file, name)
7610        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7611        .collect::<Vec<_>>();
7612    if !global_fields.is_empty() {
7613        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7614            global_fields,
7615            anchor,
7616            ctx,
7617        ));
7618    }
7619    MacroReplacementReceiver::Units(
7620        ctx.visibility
7621            .resolve_type(ctx.file, name)
7622            .into_iter()
7623            .collect(),
7624    )
7625}
7626
7627/// Type the locals a macro replacement declares, keyed by declared name.
7628///
7629/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
7630/// serves: the receiver's type is stated inside the replacement itself. A name
7631/// whose declared type does not resolve is kept with no unit, so a call on it
7632/// stays silent instead of falling through to file-scope name lookup.
7633fn macro_replacement_local_receivers(
7634    statements: Node<'_>,
7635    body: &ParsedReplacementBody,
7636    ctx: &ScanCtx<'_>,
7637) -> HashMap<String, Option<CodeUnit>> {
7638    let mut locals = HashMap::default();
7639    let mut stack = vec![statements];
7640    while let Some(current) = stack.pop() {
7641        push_named_children_reversed(current, &mut stack);
7642        if current.kind() != "declaration" {
7643            continue;
7644        }
7645        let Some(type_node) = current
7646            .child_by_field_name("type")
7647            .or_else(|| first_type_child(current))
7648        else {
7649            continue;
7650        };
7651        let unit = macro_replacement_declared_unit(type_node, body, ctx);
7652        let mut cursor = current.walk();
7653        for child in current.named_children(&mut cursor) {
7654            let declarator = if child.kind() == "init_declarator" {
7655                child.child_by_field_name("declarator")
7656            } else {
7657                is_declarator_node(child).then_some(child)
7658            };
7659            let Some(name) =
7660                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7661            else {
7662                continue;
7663            };
7664            locals.insert(name, unit.clone());
7665        }
7666    }
7667    locals
7668}
7669
7670fn macro_replacement_declared_unit(
7671    type_node: Node<'_>,
7672    body: &ParsedReplacementBody,
7673    ctx: &ScanCtx<'_>,
7674) -> Option<CodeUnit> {
7675    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7676    let unit = match ctx
7677        .visibility
7678        .resolve_type_node_result(ctx.file, type_node, &body.source)
7679    {
7680        Ok(Some(unit)) => Some(unit),
7681        Ok(None) => ctx
7682            .visibility
7683            .canonical_type_for_reference(ctx.file, &name)
7684            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7685        Err(_) => None,
7686    }?;
7687    canonical_receiver_unit(&unit, ctx)
7688}
7689
7690/// Whether the queried member is a candidate this file can see.
7691///
7692/// A macro parameter receiver has no type at the definition, so a member call
7693/// on it is at most unproven. Requiring the target to be visible here keeps a
7694/// same-named member of an unrelated translation unit out of the result.
7695fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7696    ctx.visibility
7697        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7698        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7699}
7700
7701fn maybe_record_recovered_relational_template_method_hit(
7702    call: RecoveredRelationalTemplateMemberCall<'_>,
7703    ctx: &mut ScanCtx<'_>,
7704) {
7705    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7706        return;
7707    }
7708    *ctx.raw_match_count += 1;
7709    if !ctx
7710        .visibility
7711        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7712        || ctx
7713            .spec
7714            .callable_arity_at(call.member.start_byte())
7715            .is_some_and(|arity| !arity.accepts(call.arity))
7716    {
7717        return;
7718    }
7719    record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7720}
7721
7722fn record_explicit_receiver_method_hit(
7723    receiver: Node<'_>,
7724    member: Node<'_>,
7725    arity: Option<usize>,
7726    ctx: &mut ScanCtx<'_>,
7727) {
7728    match explicit_receiver_target_resolution(receiver, arity, ctx) {
7729        MethodReceiverTargetResolution::Target
7730            if receiver_is_self_like(
7731                receiver,
7732                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7733            ) =>
7734        {
7735            push_self_receiver_hit(member, ctx);
7736        }
7737        MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7738        MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7739        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7740    }
7741}
7742
7743fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7744    if node.kind() != "qualified_identifier" {
7745        return None;
7746    }
7747    let parameter = node
7748        .parent()
7749        .filter(|parent| parent.kind() == "parameter_declaration")?;
7750    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7751    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7752    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7753    // and `(arg)` as an abstract function declarator. Ordinary qualified
7754    // parameter types have named/pointer/reference declarators instead.
7755    if parameter.child_by_field_name("type") != Some(node)
7756        || parameter_declarator.kind() != "abstract_function_declarator"
7757    {
7758        return None;
7759    }
7760    let parameter_list = parameter
7761        .parent()
7762        .filter(|parent| parent.kind() == "parameter_list")?;
7763    if parameter_list.named_child_count() != 1 {
7764        return None;
7765    }
7766    let function_declarator = parameter_list
7767        .parent()
7768        .filter(|parent| parent.kind() == "function_declarator")?;
7769    if function_declarator
7770        .child_by_field_name("declarator")
7771        .is_none_or(|declarator| declarator.kind() != "identifier")
7772        || function_declarator
7773            .parent()
7774            .is_none_or(|parent| parent.kind() != "declaration")
7775    {
7776        return None;
7777    }
7778    node.child_by_field_name("name")
7779}
7780
7781fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7782    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7783        return;
7784    };
7785    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7786        return;
7787    }
7788    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7789        return;
7790    };
7791    *ctx.raw_match_count += 1;
7792    let owner_resolution = qualified_owner_components(imported, ctx.source)
7793        .map(|qualified| {
7794            let lexical_scope = match enclosing_lexical_scope_components(
7795                imported,
7796                &ctx.analyzer,
7797                ctx.visibility,
7798                ctx.file,
7799                ctx.source,
7800            ) {
7801                LexicalScopeResolution::Resolved(scope) => scope,
7802                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7803                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7804            };
7805            ctx.visibility.resolve_type_components_lexically(
7806                &ctx.analyzer,
7807                ctx.file,
7808                &qualified.names,
7809                qualified.global,
7810                &lexical_scope,
7811            )
7812        })
7813        .unwrap_or(LexicalTypeResolution::Missing);
7814    let matches_target_owner = matches!(
7815        owner_resolution,
7816        LexicalTypeResolution::Resolved {
7817            ref unit,
7818            ref candidates,
7819            ..
7820        } if same_visible_symbol(unit, target_owner)
7821            || candidates
7822                .iter()
7823                .any(|candidate| same_visible_symbol(candidate, target_owner))
7824    );
7825    if !matches_target_owner {
7826        match owner_resolution {
7827            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7828                push_unproven_hit(imported, ctx);
7829            }
7830            LexicalTypeResolution::Resolved { .. } => {}
7831        }
7832        return;
7833    }
7834    match visible_target_callable_member_resolution(target_owner, ctx) {
7835        VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7836        VisibleTargetCallableMemberResolution::NonCallable => {}
7837        VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7838    }
7839}
7840
7841enum VisibleTargetCallableMemberResolution {
7842    Target,
7843    NonCallable,
7844    Unresolved,
7845}
7846
7847fn visible_target_callable_member_resolution(
7848    owner: &CodeUnit,
7849    ctx: &ScanCtx<'_>,
7850) -> VisibleTargetCallableMemberResolution {
7851    match ctx
7852        .visibility
7853        .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7854    {
7855        VisibleMemberResolution::Callable(candidates)
7856            if candidates.iter().all(|candidate| {
7857                ctx.target_group.contains(candidate)
7858                    || ctx
7859                        .target_group
7860                        .iter()
7861                        .any(|target| same_visible_symbol(candidate, target))
7862            }) =>
7863        {
7864            VisibleTargetCallableMemberResolution::Target
7865        }
7866        VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7867        VisibleMemberResolution::Callable(_)
7868        | VisibleMemberResolution::AmbiguousKind
7869        | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7870    }
7871}
7872
7873fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7874    let name = node_text(node, ctx.source);
7875    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7876    let key = (namespace.clone(), name.to_string());
7877    if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7878        return resolved;
7879    }
7880    let resolved = ctx
7881        .visibility
7882        .visible_identifier_candidates(ctx.file, name)
7883        .any(|unit| {
7884            unit.is_function()
7885                && type_owner_of(&ctx.analyzer, unit).is_none()
7886                && unit.package_name() == namespace
7887        });
7888    ctx.lexical_free_function_cache
7889        .borrow_mut()
7890        .insert(key, resolved);
7891    resolved
7892}
7893
7894fn maybe_record_qualified_method_value_hit(
7895    qualified: Node<'_>,
7896    member: Node<'_>,
7897    ctx: &mut ScanCtx<'_>,
7898) {
7899    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7900        return;
7901    }
7902    *ctx.raw_match_count += 1;
7903    let resolution =
7904        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7905    match resolution {
7906        LexicalCallableValueResolution::Type(resolved_owner) => {
7907            let Some(owner) = ctx.spec.owner.as_ref() else {
7908                push_unproven_hit(member, ctx);
7909                return;
7910            };
7911            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7912                if same_visible_symbol(&resolved_owner, owner) {
7913                    push_unproven_hit(member, ctx);
7914                }
7915                return;
7916            }
7917            // An explicitly qualified method value remains an external
7918            // reference even when its owner is the enclosing class.
7919            match visible_target_callable_member_resolution(owner, ctx) {
7920                VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7921                VisibleTargetCallableMemberResolution::NonCallable => {}
7922                VisibleTargetCallableMemberResolution::Unresolved => {
7923                    push_unproven_hit(member, ctx);
7924                }
7925            }
7926        }
7927        LexicalCallableValueResolution::FreeFunction(_) => {}
7928        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7929            push_unproven_hit(member, ctx);
7930        }
7931    }
7932}
7933
7934fn qualified_callable_value_resolution(
7935    qualified: Node<'_>,
7936    member_name: &str,
7937    ctx: &ScanCtx<'_>,
7938) -> LexicalCallableValueResolution {
7939    let Some((owner_components, global)) =
7940        qualified_callable_owner_components(qualified, ctx.source)
7941    else {
7942        return LexicalCallableValueResolution::Missing;
7943    };
7944    let lexical_scope = if global {
7945        Vec::new()
7946    } else {
7947        match enclosing_lexical_scope_components(
7948            qualified,
7949            &ctx.analyzer,
7950            ctx.visibility,
7951            ctx.file,
7952            ctx.source,
7953        ) {
7954            LexicalScopeResolution::Resolved(scope) => scope,
7955            LexicalScopeResolution::Ambiguous => {
7956                return LexicalCallableValueResolution::Ambiguous;
7957            }
7958            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7959        }
7960    };
7961    if let Some(target_owner) = ctx.spec.owner.as_ref()
7962        && let LexicalTypeResolution::Resolved { unit, .. } =
7963            resolve_type_components_lexically_at_for_target_with_scope_cache(
7964                qualified,
7965                &owner_components,
7966                global,
7967                &ctx.analyzer,
7968                ctx.visibility,
7969                &ctx.ordinary_type_imports,
7970                ctx.file,
7971                ctx.source,
7972                target_owner,
7973                false,
7974                Some(&ctx.lexical_scope_cache),
7975            )
7976    {
7977        return LexicalCallableValueResolution::Type(unit);
7978    }
7979    ctx.visibility.resolve_callable_value_components_lexically(
7980        &ctx.analyzer,
7981        ctx.file,
7982        &owner_components,
7983        member_name,
7984        global,
7985        &lexical_scope,
7986    )
7987}
7988
7989fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7990    let Some(owner) = ctx.spec.owner.as_ref() else {
7991        return true;
7992    };
7993    if ctx.spec.param_types.is_none() {
7994        return true;
7995    }
7996    let candidates = ctx
7997        .visibility
7998        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7999        .into_iter()
8000        .filter(|unit| unit.is_function())
8001        .cloned()
8002        .collect::<Vec<_>>();
8003    callable_candidates_may_match_target(call, candidates, ctx)
8004}
8005
8006fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8007    let Some(function) = function_definition_name_node(node) else {
8008        return;
8009    };
8010    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
8011        return;
8012    }
8013    *ctx.raw_match_count += 1;
8014    if !function_definition_signature_matches_target(node, ctx) {
8015        return;
8016    }
8017    if node_inside_target_declaration(function, ctx) {
8018        return;
8019    }
8020    if is_structurally_qualified(function) {
8021        match qualified_owner_resolution(function, ctx) {
8022            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
8023            QualifiedOwnerResolution::NonTarget => {}
8024            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8025        }
8026        return;
8027    }
8028    match definition_name_target_resolution(function, TargetKind::Method, ctx) {
8029        DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
8030        DefinitionNameTargetResolution::KnownNonTarget => {
8031            // A method definition for another visible owner is a proven non-match.
8032        }
8033        DefinitionNameTargetResolution::Unresolved
8034            if known_non_target_owner_context(function, ctx) => {}
8035        DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
8036    }
8037}
8038
8039pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8040    let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
8041    ctx.target_declaration_ranges
8042        .iter()
8043        .any(|range| range.contains(&node_range))
8044}
8045
8046fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
8047    let mut receiver = None;
8048    let mut cursor = node.walk();
8049    for child in node.named_children(&mut cursor) {
8050        if child.kind() == "argument_list" {
8051            continue;
8052        }
8053        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
8054            return receiver.map(|receiver| (receiver, operator));
8055        }
8056        if receiver.is_none() {
8057            receiver = Some(child);
8058        }
8059    }
8060    None
8061}
8062
8063/// Return the name node of a C++ function definition's declarator.
8064pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
8065    if node.kind() != "function_definition" {
8066        return None;
8067    }
8068    node.child_by_field_name("declarator")
8069        .and_then(declarator_name_node)
8070}
8071
8072fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
8073    function_definition_name_node(node)
8074}
8075
8076fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8077    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8078        return true;
8079    };
8080    let Some(candidate) = canonical_callable_at_node(
8081        node,
8082        &ctx.analyzer,
8083        ctx.visibility,
8084        ctx.file,
8085        ctx.source,
8086        ctx.spec.kind,
8087    ) else {
8088        return false;
8089    };
8090    let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
8091        return false;
8092    };
8093    if !callable_arity_matches(expected, actual_arity) {
8094        return false;
8095    }
8096    ctx.spec.param_types.as_ref().is_none_or(|expected| {
8097        cpp_callable_parameter_types(&ctx.analyzer, &candidate)
8098            .is_some_and(|actual| actual == *expected)
8099    })
8100}
8101
8102fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8103    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
8104        return true;
8105    };
8106    cpp_callable_arity(&ctx.analyzer, candidate)
8107        .is_some_and(|actual| callable_arity_matches(expected, actual))
8108}
8109
8110fn callable_arity_matches(
8111    expected: brokk_bifrost_core::analyzer::model::CallableArity,
8112    actual: brokk_bifrost_core::analyzer::model::CallableArity,
8113) -> bool {
8114    expected.accepts(actual.total())
8115}
8116
8117fn canonical_callable_at_node(
8118    node: Node<'_>,
8119    analyzer: &CppGraphSource<'_>,
8120    visibility: &VisibilityIndex<'_>,
8121    file: &ProjectFile,
8122    source: &str,
8123    kind: TargetKind,
8124) -> Option<CodeUnit> {
8125    let name = callable_name_node(node)?;
8126    let name_range = (name.start_byte(), name.end_byte());
8127    let name_text = node_text(name, source);
8128    for candidate in visibility.named_candidates(file, name_text, kind) {
8129        if candidate.source() != file || !candidate.is_function() {
8130            continue;
8131        }
8132        let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
8133            continue;
8134        };
8135        for occurrence in occurrences {
8136            if occurrence
8137                .name_range
8138                .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
8139            {
8140                return Some(candidate);
8141            }
8142        }
8143    }
8144    None
8145}
8146
8147fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
8148    let name = if node.kind() == "function_definition" {
8149        function_definition_name_node(node)?
8150    } else {
8151        node.child_by_field_name("declarator")
8152            .and_then(declarator_name_node)?
8153    };
8154    Some(function_terminal_node(name))
8155}
8156
8157fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
8158    name_matches_callable(node_text(function_terminal_node(node), source), expected)
8159}
8160
8161enum DefinitionNameTargetResolution {
8162    Target,
8163    KnownNonTarget,
8164    Unresolved,
8165}
8166
8167fn definition_name_target_resolution(
8168    function: Node<'_>,
8169    target_kind: TargetKind,
8170    ctx: &ScanCtx<'_>,
8171) -> DefinitionNameTargetResolution {
8172    let candidates = definition_name_candidates(function, ctx);
8173    if candidates
8174        .iter()
8175        .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
8176        .any(|name| {
8177            ctx.visibility
8178                .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
8179        })
8180    {
8181        DefinitionNameTargetResolution::Target
8182    } else if candidates.iter().any(|name| {
8183        ctx.visibility
8184            .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
8185    }) {
8186        DefinitionNameTargetResolution::KnownNonTarget
8187    } else {
8188        DefinitionNameTargetResolution::Unresolved
8189    }
8190}
8191
8192fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
8193    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
8194    if raw.is_empty() {
8195        return Vec::new();
8196    }
8197    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
8198        return vec![raw];
8199    };
8200    if !raw.contains("::") {
8201        return vec![format!("{namespace}::{raw}")];
8202    }
8203    // fqname-M4: peeks at the raw first `::`-split token, including the empty
8204    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
8205    // shape as rust's `rust_reference_looks_external`); the shared structured
8206    // splitter filters empty segments, which would shift "which token is
8207    // first" for that one lead-`::` shape and is not proven equivalent here.
8208    if raw
8209        .split("::")
8210        .next()
8211        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
8212    {
8213        vec![format!("{namespace}::{raw}"), raw]
8214    } else {
8215        vec![raw]
8216    }
8217}
8218
8219fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8220    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
8221}
8222
8223/// Whether a name leaf the field scans reached sits in a value position.
8224///
8225/// Every kind those scans admit already names a value except `type_identifier`.
8226/// tree-sitter spells a non-type template argument with type syntax, so the
8227/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
8228/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
8229/// Admit that leaf only where no visible type explains the spelling, which is
8230/// the order forward navigation applies before it reads the leaf in the value
8231/// namespace (#2556). Every other `type_identifier` is a type reference and
8232/// belongs to the type scan.
8233fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8234    node.kind() != "type_identifier"
8235        || (is_type_shaped_template_argument_name(node)
8236            && ctx
8237                .visibility
8238                .resolve_type(ctx.file, node_text(node, ctx.source))
8239                .is_none())
8240}
8241
8242fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8243    if is_c_offsetof_member_node(node) {
8244        return;
8245    }
8246    if matches!(node.kind(), "identifier" | "field_identifier")
8247        && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8248            .is_some()
8249    {
8250        return;
8251    }
8252    if !matches!(
8253        node.kind(),
8254        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
8255    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8256        || !scan_leaf_is_value_position(node, ctx)
8257        || is_declaration_name(node)
8258        || is_member_field_own_declarator(node, ctx)
8259        || is_selected_field_expression_member_descendant(node)
8260        || is_nested_in_qualified_identifier(node)
8261    {
8262        return;
8263    }
8264    *ctx.raw_match_count += 1;
8265    if global_field_resolves_to_target(node, ctx) {
8266        push_hit(node, ctx);
8267    } else if global_field_is_known_non_target(node, ctx) {
8268    } else {
8269        push_unproven_hit(node, ctx);
8270    }
8271}
8272
8273/// Whether `node` belongs to the selected-member side of any enclosing field
8274/// expression. A reference may be nested arbitrarily inside the receiver side
8275/// (for example, an argument to a call-built fluent receiver), so direct child
8276/// equality is insufficient: classify each ancestor by structured subtree
8277/// containment instead.
8278fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
8279    let candidate = node;
8280    while let Some(parent) = node.parent() {
8281        if parent.kind() == "field_expression" {
8282            if let Some(field) = parent.child_by_field_name("field")
8283                && node_is_within(field, candidate)
8284            {
8285                // The `template` disambiguator in `receiver.template f<Arg>()`
8286                // wraps the selected member in a `dependent_name` whose single
8287                // named child is the `template_*` node the plain spelling puts
8288                // directly under `field`. Unwrap it so both spellings reach the
8289                // same name/arguments split (#2196).
8290                let selected = match field.kind() {
8291                    "dependent_name" => field.named_child(0).unwrap_or(field),
8292                    _ => field,
8293                };
8294                let selected_name = match selected.kind() {
8295                    "template_method" | "template_function" | "template_type" => {
8296                        selected.child_by_field_name("name").unwrap_or(selected)
8297                    }
8298                    _ => selected,
8299                };
8300                if node_is_within(selected_name, candidate) {
8301                    return true;
8302                }
8303                // A template argument is structurally inside the field subtree,
8304                // but it is an independent reference rather than the selected
8305                // member name.
8306                node = parent;
8307                continue;
8308            }
8309            let receiver = parent
8310                .child_by_field_name("argument")
8311                .or_else(|| parent.child_by_field_name("object"))
8312                .or_else(|| parent.named_child(0));
8313            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8314                // Unknown grammar shape inside a field expression: fail closed
8315                // rather than treating it as a receiver reference.
8316                return true;
8317            }
8318        }
8319        node = parent;
8320    }
8321    false
8322}
8323
8324fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8325    let text = node_text(node, ctx.source);
8326    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8327        return false;
8328    }
8329    if text.contains("::") {
8330        return ctx.visibility.contains_named_symbol(
8331            ctx.file,
8332            text,
8333            TargetKind::GlobalField,
8334            &ctx.spec.target,
8335        );
8336    }
8337    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8338        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8339    {
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(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8348        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8349            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8350                brokk_bifrost_core::analyzer::Language::Cpp,
8351                &namespace,
8352            ) == indexed_scope
8353        })
8354    {
8355        return ctx.visibility.contains_named_symbol(
8356            ctx.file,
8357            text,
8358            TargetKind::GlobalField,
8359            &ctx.spec.target,
8360        );
8361    }
8362    bare_global_field_uniquely_resolves_to_target(text, ctx)
8363}
8364
8365fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8366    let mut matched_target = false;
8367    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8368        if !has_persisted_global_field_identity(unit)
8369            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8370        {
8371            continue;
8372        }
8373        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8374            continue;
8375        }
8376        if same_visible_global_field_symbol(
8377            &ctx.analyzer,
8378            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8379            unit,
8380            &ctx.spec.target,
8381        ) {
8382            matched_target = true;
8383        } else {
8384            return false;
8385        }
8386    }
8387    matched_target
8388}
8389
8390pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8391    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
8392    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
8393    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
8394    // analyzer for every same-named candidate's parent.
8395    unit.is_field() && !unit.short_name().contains('.')
8396}
8397
8398fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8399    let text = node_text(node, ctx.source);
8400    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8401        return true;
8402    }
8403    if text.contains("::") {
8404        return ctx.visibility.resolve_known_non_target(
8405            ctx.file,
8406            text,
8407            TargetKind::GlobalField,
8408            &ctx.spec.target,
8409        );
8410    }
8411    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8412        return false;
8413    };
8414    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8415        && ctx
8416            .visibility
8417            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8418            .any(|unit| {
8419                has_persisted_global_field_identity(unit)
8420                    && unit.identifier() == ctx.spec.member_name
8421                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8422                    && !same_visible_global_field_symbol(
8423                        &ctx.analyzer,
8424                        &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8425                        unit,
8426                        &ctx.spec.target,
8427                    )
8428            })
8429}
8430
8431fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8432    if is_c_offsetof_member_node(node) {
8433        maybe_record_c_offsetof_field_hit(node, ctx);
8434        return;
8435    }
8436    if node.kind() == "field_expression" {
8437        let Some(field) = node.child_by_field_name("field") else {
8438            return;
8439        };
8440        if node_text(field, ctx.source) != ctx.spec.member_name {
8441            return;
8442        }
8443        *ctx.raw_match_count += 1;
8444        if !ctx.visibility.declaration_guards_compatible_at_reference(
8445            &ctx.analyzer,
8446            ctx.file,
8447            &ctx.spec.target,
8448            field,
8449        ) {
8450            return;
8451        }
8452        let receiver = node
8453            .child_by_field_name("argument")
8454            .or_else(|| node.child_by_field_name("object"));
8455        let receiver_resolution =
8456            receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8457        match receiver_resolution {
8458            Some(MethodReceiverTargetResolution::Target)
8459                if ctx.visibility.declaration_visible_at_reference(
8460                    &ctx.analyzer,
8461                    ctx.file,
8462                    &ctx.spec.target,
8463                    field,
8464                ) || (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
8465                    && ctx.spec.target.source() == ctx.file
8466                    && ctx
8467                        .visibility
8468                        .external_type_candidate_guard_compatible_in_context(
8469                            &ctx.analyzer,
8470                            ctx.file,
8471                            &ctx.spec.target,
8472                            field,
8473                        )) =>
8474            {
8475                // The receiver proves the C++ member owner, including fields
8476                // recovered without callable activation facts. Its guard still
8477                // needs implication and stability, even in the same file.
8478                push_hit(field, ctx)
8479            }
8480            Some(
8481                MethodReceiverTargetResolution::Target | MethodReceiverTargetResolution::Missing,
8482            )
8483            | None => push_unproven_hit(field, ctx),
8484            Some(
8485                MethodReceiverTargetResolution::NonTarget
8486                | MethodReceiverTargetResolution::Ambiguous,
8487            ) => {}
8488        }
8489        return;
8490    }
8491
8492    if matches!(node.kind(), "identifier" | "field_identifier")
8493        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8494        && let Some(designator_owner) =
8495            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8496    {
8497        *ctx.raw_match_count += 1;
8498        match designator_owner {
8499            DesignatedInitializerOwner::Resolved(owner)
8500                if ctx
8501                    .spec
8502                    .owner
8503                    .as_ref()
8504                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8505            {
8506                push_hit(node, ctx);
8507            }
8508            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8509            DesignatedInitializerOwner::Resolved(_) => {}
8510        }
8511        return;
8512    }
8513
8514    let qualified_member_name_matches =
8515        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8516            && cpp_name_component_nodes(node)
8517                .and_then(|components| components.last().copied())
8518                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8519    if !matches!(
8520        node.kind(),
8521        "identifier"
8522            | "field_identifier"
8523            | "qualified_identifier"
8524            | "scoped_identifier"
8525            | "type_identifier"
8526    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8527        && !qualified_member_name_matches)
8528        || !scan_leaf_is_value_position(node, ctx)
8529        || is_declaration_name(node)
8530        || is_member_field_own_declarator(node, ctx)
8531        || is_selected_field_expression_member_descendant(node)
8532        || is_nested_in_qualified_identifier(node)
8533    {
8534        return;
8535    }
8536    *ctx.raw_match_count += 1;
8537    if is_structurally_qualified(node) {
8538        match qualified_owner_resolution(node, ctx) {
8539            QualifiedOwnerResolution::Target => push_hit(node, ctx),
8540            QualifiedOwnerResolution::NonTarget => {}
8541            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8542        }
8543        return;
8544    }
8545    let text = node_text(node, ctx.source);
8546    if ctx.local_shadows.is_shadowed(text) {
8547        return;
8548    }
8549    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8550        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8551    let owner_context = structured_owner_context_resolution(node, ctx);
8552    if matches!(
8553        owner_context,
8554        StructuredOwnerContextResolution::SelfTarget
8555            | StructuredOwnerContextResolution::InheritedTarget
8556    ) || unscoped_enum_match
8557    {
8558        push_hit(node, ctx);
8559    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8560        .then_some(ctx.spec.owner.as_ref())
8561        .flatten()
8562    {
8563        let resolution =
8564            match resolve_active_using_enum_member(node, ctx) {
8565                ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8566                ActiveUsingEnumMemberResolution::Class(resolution) => {
8567                    if direct_class_member_shadows(node, ctx) {
8568                        return;
8569                    }
8570                    resolution
8571                }
8572                ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8573                    if let Some(owner) = structured_enclosing_owner(node, ctx) {
8574                        if direct_class_member_shadows(node, ctx) {
8575                            return;
8576                        }
8577                        let complete_same_file_leaf =
8578                            owner.source() == ctx.file
8579                                && ctx.analyzer.type_hierarchy_provider().is_some_and(
8580                                    |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
8581                                );
8582                        if !complete_same_file_leaf {
8583                            push_unproven_hit(node, ctx);
8584                            return;
8585                        }
8586                    }
8587                    match owner_context {
8588                        StructuredOwnerContextResolution::SelfTarget
8589                        | StructuredOwnerContextResolution::InheritedTarget
8590                        | StructuredOwnerContextResolution::NonTarget => return,
8591                        StructuredOwnerContextResolution::Ambiguous => {
8592                            push_unproven_hit(node, ctx);
8593                            return;
8594                        }
8595                        StructuredOwnerContextResolution::Missing => {}
8596                    }
8597                    if namespace_value_shadows(node, ctx) {
8598                        return;
8599                    }
8600                    resolution
8601                }
8602                ActiveUsingEnumMemberResolution::Missing => {
8603                    if direct_class_member_shadows(node, ctx)
8604                        || (structured_enclosing_owner(node, ctx).is_none()
8605                            && namespace_value_shadows(node, ctx))
8606                    {
8607                        return;
8608                    }
8609                    UsingEnumMemberResolution::Missing
8610                }
8611            };
8612        match resolution {
8613            UsingEnumMemberResolution::Resolved { owner, member }
8614                if same_visible_symbol(&owner, target_owner)
8615                    && same_visible_symbol(&member, &ctx.spec.target) =>
8616            {
8617                push_hit(node, ctx);
8618            }
8619            UsingEnumMemberResolution::Resolved { .. } => {}
8620            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8621                push_unproven_hit(node, ctx)
8622            }
8623        }
8624    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8625        push_unproven_hit(node, ctx);
8626    }
8627}
8628
8629fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8630    if node_text(node, ctx.source) != ctx.spec.member_name {
8631        return;
8632    }
8633    *ctx.raw_match_count += 1;
8634    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8635        push_unproven_hit(node, ctx);
8636        return;
8637    };
8638    let owner = match resolve_type_node_lexically(
8639        type_reference,
8640        &ctx.analyzer,
8641        ctx.visibility,
8642        &ctx.ordinary_type_imports,
8643        ctx.file,
8644        ctx.source,
8645    ) {
8646        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8647        LexicalTypeResolution::Resolved { .. }
8648        | LexicalTypeResolution::Ambiguous
8649        | LexicalTypeResolution::Missing => {
8650            push_unproven_hit(member, ctx);
8651            return;
8652        }
8653    };
8654    let candidates = ctx
8655        .visibility
8656        .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8657        .into_iter()
8658        .filter(|candidate| candidate.is_field())
8659        .collect::<Vec<_>>();
8660    if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8661        push_hit(member, ctx);
8662    } else if candidates.len() > 1 {
8663        push_unproven_hit(member, ctx);
8664    }
8665}
8666
8667enum ActiveUsingEnumMemberResolution {
8668    Block(UsingEnumMemberResolution),
8669    Class(UsingEnumMemberResolution),
8670    Namespace(UsingEnumMemberResolution),
8671    Missing,
8672}
8673
8674fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8675    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8676        ctx.visibility
8677            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8678            .into_iter()
8679            .next()
8680            .is_some()
8681    })
8682}
8683
8684fn resolve_active_using_enum_member(
8685    node: Node<'_>,
8686    ctx: &ScanCtx<'_>,
8687) -> ActiveUsingEnumMemberResolution {
8688    let block =
8689        ctx.using_enum_owners
8690            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8691    if !matches!(block, UsingEnumMemberResolution::Missing) {
8692        return ActiveUsingEnumMemberResolution::Block(block);
8693    }
8694    let class = structured_enclosing_owner(node, ctx);
8695    let namespace = enclosing_namespace_components(node, ctx.source);
8696    match ctx.semantic_using_enum_owners.resolve_member(
8697        ctx.visibility,
8698        ctx.file,
8699        class.as_ref(),
8700        &namespace,
8701        node.start_byte(),
8702        &ctx.spec.member_name,
8703    ) {
8704        SemanticUsingEnumMemberResolution::Class(resolution) => {
8705            ActiveUsingEnumMemberResolution::Class(resolution)
8706        }
8707        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8708            ActiveUsingEnumMemberResolution::Namespace(resolution)
8709        }
8710        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8711    }
8712}
8713
8714fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8715    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8716    !matches!(
8717        resolve_namespace_value(
8718            &ctx.analyzer,
8719            ctx.visibility,
8720            ctx.file,
8721            &namespace,
8722            &ctx.spec.member_name,
8723            node.start_byte(),
8724        ),
8725        NamespaceValueResolution::Missing
8726    )
8727}
8728
8729/// Whether `node` is a component of an enclosing qualified identifier's own
8730/// `scope`/`name` path, so the outer `qualified_identifier` is the single
8731/// reference surfaced for it.
8732///
8733/// A qualified identifier owns only that path. Everything else beneath it is an
8734/// independent reference that its own target scanner must resolve:
8735///
8736/// - `Owner::Template<argument>` holds each template argument outside the path.
8737/// - Error recovery can hang a complete member initializer off an `ERROR` child
8738///   of a synthetic qualified identifier, where no structured path exists.
8739/// - The grammar accepts a `pointer_type_declarator` as a qualified
8740///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
8741///   folds a whole parameter list under one `qualified_identifier`. Parameter
8742///   default values there are ordinary value expressions (#2548).
8743///
8744/// Follow the structured `scope`/`name` links rather than every ancestor, which
8745/// covers all three cases with one rule.
8746/// Whether `node` lies on the scope/name path of an enclosing
8747/// `qualified_identifier`, which owns the complete name. The grammar nests
8748/// `::A::b` and `n::A::b` as a qualified suffix `A::b`; resolving that suffix
8749/// on its own would drop the leading `::` or `n::` qualifier.
8750fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8751    let mut current = node;
8752    while let Some(parent) = current.parent() {
8753        let on_name_path = ["scope", "name"].into_iter().any(|field| {
8754            parent
8755                .child_by_field_name(field)
8756                .is_some_and(|component| same_node(component, current))
8757        });
8758        if !on_name_path {
8759            return false;
8760        }
8761        if parent.kind() == "qualified_identifier" {
8762            return true;
8763        }
8764        current = parent;
8765    }
8766    false
8767}
8768
8769fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8770    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8771}
8772
8773/// What a receiver spelled as a qualified name denotes.
8774///
8775/// `ns::value.run()` reads the member off the declared type of the variable
8776/// `ns::value`; a type path spelled as a receiver reads it off the type itself.
8777/// Forward navigation and the inverse walk both ask this before typing such a
8778/// receiver, so the two directions accept the same receiver spellings (#2547).
8779pub enum QualifiedReceiverBase {
8780    /// Every visible declaration of the one variable the path names.
8781    Variable(Vec<CodeUnit>),
8782    /// The type the path names when it names no variable.
8783    Type(Option<CodeUnit>),
8784    /// The path names more than one variable; typing either would be a guess.
8785    Ambiguous,
8786}
8787
8788pub fn qualified_receiver_base(
8789    visibility: &VisibilityIndex<'_>,
8790    file: &ProjectFile,
8791    receiver: Node<'_>,
8792    source: &str,
8793) -> QualifiedReceiverBase {
8794    let reference = node_text(receiver, source);
8795    let fields = visibility
8796        .named_candidates(file, reference, TargetKind::GlobalField)
8797        .into_iter()
8798        .filter(has_persisted_global_field_identity)
8799        .collect::<Vec<_>>();
8800    let Some(first) = fields.first() else {
8801        return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8802    };
8803    if fields
8804        .iter()
8805        .skip(1)
8806        .any(|field| !same_visible_symbol(first, field))
8807    {
8808        return QualifiedReceiverBase::Ambiguous;
8809    }
8810    QualifiedReceiverBase::Variable(fields)
8811}
8812
8813fn receiver_type_units_with_budget(
8814    node: Node<'_>,
8815    source: &str,
8816    ctx: &ScanCtx<'_>,
8817    remaining_call_depth: usize,
8818) -> Vec<CodeUnit> {
8819    let mut current = node;
8820    let mut member_chain = Vec::new();
8821    let mut base_units = loop {
8822        match current.kind() {
8823            "field_expression" => {
8824                let Some(member) = current.child_by_field_name("field") else {
8825                    return Vec::new();
8826                };
8827                let Some(receiver) = current
8828                    .child_by_field_name("argument")
8829                    .or_else(|| current.child_by_field_name("object"))
8830                    .or_else(|| current.named_child(0))
8831                else {
8832                    return Vec::new();
8833                };
8834                member_chain.push(node_text(member, source));
8835                current = receiver;
8836            }
8837            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8838                let Some(inner) = current
8839                    .child_by_field_name("argument")
8840                    .or_else(|| current.named_child(0))
8841                else {
8842                    return Vec::new();
8843                };
8844                current = inner;
8845            }
8846            // Tree-sitter uses `field_identifier` for an unqualified member
8847            // field when it appears as the base of another field expression
8848            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
8849            // same structured binding and enclosing-owner paths as an
8850            // ordinary identifier; falling through to `resolve_type` would
8851            // treat the field name as a type and lose the receiver identity.
8852            "identifier" | "field_identifier" => {
8853                let name = node_text(current, source);
8854                // Function-like macro locals are visible in the replacement
8855                // range after substitution and shadow caller bindings. The
8856                // receiver scan has no root field, so recover the source tree
8857                // root before asking the visibility index for that binding.
8858                let mut root = current;
8859                while let Some(parent) = ctx.ancestry.parent(root) {
8860                    root = parent;
8861                }
8862                if let Some(binding) = ctx.visibility.macro_local_binding_at(
8863                    ctx.file,
8864                    root,
8865                    source,
8866                    current.start_byte(),
8867                    current.end_byte(),
8868                ) && binding.name == name
8869                {
8870                    let normalized = normalize_cpp_type_name(&binding.type_name);
8871                    let unit = binding
8872                        .proven_unit
8873                        .clone()
8874                        .or_else(|| {
8875                            binding.type_node.and_then(|type_node| {
8876                                resolve_receiver_type_node(type_node, ctx).ok().flatten()
8877                            })
8878                        })
8879                        .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8880                    // Keep the macro declaration's shadowing boundary even
8881                    // when its type is unavailable: an unresolved macro local
8882                    // cannot be reinterpreted as a static type or outer value.
8883                    break unit.into_iter().collect();
8884                }
8885                let local = ctx.bindings.resolve_symbol(name);
8886                if let Some(bindings) = local.as_precise() {
8887                    break receiver_units_from_bindings(current, bindings, ctx);
8888                }
8889                if ctx.bindings.is_shadowed(name) {
8890                    return Vec::new();
8891                }
8892                let owner = structured_enclosing_owner(current, ctx)
8893                    .filter(CodeUnit::is_class)
8894                    .or_else(|| {
8895                        enclosing_context(current, ctx)
8896                            .owner
8897                            .filter(CodeUnit::is_class)
8898                    });
8899                if let Some(owner) = owner {
8900                    // The enclosing class is only the search root: an implicit
8901                    // member-field receiver can be declared on any base, so the
8902                    // declaring owner comes from the same hierarchy walk the
8903                    // member chain below uses, not from an exact-parent match.
8904                    let declaring_owner = match resolve_declaring_member_owner(
8905                        &ctx.analyzer,
8906                        ctx.visibility,
8907                        ctx.file,
8908                        &owner,
8909                        name,
8910                    ) {
8911                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8912                        EnclosingMemberOwnerResolution::Missing => None,
8913                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8914                    };
8915                    if let Some(declaring_owner) = declaring_owner {
8916                        let implicit_fields = ctx
8917                            .visibility
8918                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8919                            .into_iter()
8920                            .filter(|unit| unit.is_field())
8921                            .collect::<Vec<_>>();
8922                        if !implicit_fields.is_empty() {
8923                            break receiver_units_from_declared_fields(
8924                                implicit_fields,
8925                                current,
8926                                ctx,
8927                            );
8928                        }
8929                    }
8930                }
8931                let global_fields = ctx
8932                    .visibility
8933                    .visible_identifier_candidates(ctx.file, name)
8934                    .filter(|unit| {
8935                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8936                    })
8937                    .collect::<Vec<_>>();
8938                if global_fields.is_empty() {
8939                    break ctx
8940                        .visibility
8941                        .resolve_type(ctx.file, name)
8942                        .into_iter()
8943                        .collect();
8944                }
8945                if let Some(first) = global_fields.first()
8946                    && global_fields.iter().skip(1).any(|field| {
8947                        !same_visible_global_field_symbol(
8948                            &ctx.analyzer,
8949                            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8950                            first,
8951                            field,
8952                        )
8953                    })
8954                {
8955                    return Vec::new();
8956                }
8957                break receiver_units_from_declared_fields(global_fields, current, ctx);
8958            }
8959            "call_expression" | "new_expression" => {
8960                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8961                    .and_then(|binding| binding.unit)
8962                    .into_iter()
8963                    .collect();
8964            }
8965            // `((T*)p)->member` is typed by what the cast spells, not by the
8966            // operand it converts (#2981). Pointer depth does not change which
8967            // type declares the member, so only the descriptor's type matters.
8968            "cast_expression" => {
8969                let Some(descriptor) = current.child_by_field_name("type") else {
8970                    return Vec::new();
8971                };
8972                let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8973                    return Vec::new();
8974                };
8975                break unit.into_iter().collect();
8976            }
8977            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8978                let name = node_text(current, source);
8979                let local = ctx.bindings.resolve_symbol(name);
8980                if let Some(bindings) = local.as_precise() {
8981                    break receiver_units_from_bindings(current, bindings, ctx);
8982                }
8983                return Vec::new();
8984            }
8985            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8986            "qualified_identifier" | "scoped_identifier" => {
8987                match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8988                    QualifiedReceiverBase::Variable(fields) => {
8989                        break receiver_units_from_declared_fields(
8990                            fields.iter().collect(),
8991                            current,
8992                            ctx,
8993                        );
8994                    }
8995                    QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
8996                    QualifiedReceiverBase::Ambiguous => return Vec::new(),
8997                }
8998            }
8999            _ => {
9000                break ctx
9001                    .visibility
9002                    .resolve_type(ctx.file, node_text(current, source))
9003                    .into_iter()
9004                    .collect();
9005            }
9006        }
9007    };
9008
9009    base_units = canonical_receiver_units(base_units, ctx);
9010    if base_units.is_empty() {
9011        return Vec::new();
9012    }
9013
9014    while let Some(member_name) = member_chain.pop() {
9015        let mut next_units = Vec::new();
9016        for owner in &base_units {
9017            let declaring_owner = match resolve_declaring_member_owner(
9018                &ctx.analyzer,
9019                ctx.visibility,
9020                ctx.file,
9021                owner,
9022                member_name,
9023            ) {
9024                EnclosingMemberOwnerResolution::Owner(owner) => owner,
9025                EnclosingMemberOwnerResolution::Missing => continue,
9026                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
9027            };
9028            let fields = ctx.visibility.visible_members_for_owner_name(
9029                ctx.file,
9030                &declaring_owner,
9031                member_name,
9032            );
9033            for field in fields.into_iter().filter(|unit| unit.is_field()) {
9034                let Some(unit) =
9035                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9036                        .and_then(|binding| binding.unit)
9037                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
9038                else {
9039                    continue;
9040                };
9041                if !next_units
9042                    .iter()
9043                    .any(|existing| same_visible_symbol(existing, &unit))
9044                {
9045                    next_units.push(unit);
9046                }
9047            }
9048        }
9049        if next_units.is_empty() {
9050            return Vec::new();
9051        }
9052        base_units = unanimous_receiver_units(next_units);
9053        if base_units.is_empty() {
9054            return Vec::new();
9055        }
9056    }
9057    base_units
9058}
9059
9060fn receiver_units_from_bindings(
9061    node: Node<'_>,
9062    bindings: &HashSet<CppScanBinding>,
9063    ctx: &ScanCtx<'_>,
9064) -> Vec<CodeUnit> {
9065    let mut units = Vec::new();
9066    for binding in bindings {
9067        let raw_unit = if let Some(unit) = &binding.unit {
9068            unit.clone()
9069        } else {
9070            let Some(type_name) = binding.type_name.as_deref() else {
9071                return Vec::new();
9072            };
9073            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
9074                return Vec::new();
9075            };
9076            unit
9077        };
9078        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
9079            // Type aliases are represented as class units. When an alias has
9080            // a dependent target, canonicalization deliberately preserves
9081            // that alias rather than inventing a concrete class. Give the
9082            // existing target-guided structured recovery a chance to prove
9083            // the queried owner before accepting that unresolved identity.
9084            if same_visible_symbol(&unit, &raw_unit)
9085                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
9086            {
9087                units.push(recovered);
9088                continue;
9089            }
9090            units.push(unit);
9091            continue;
9092        }
9093        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
9094            units.push(unit);
9095            continue;
9096        }
9097        return Vec::new();
9098    }
9099    unanimous_receiver_units(units)
9100}
9101
9102/// Resolve a using-alias receiver from its declaration's structured RHS when
9103/// the alias target index cannot cross a malformed namespace-sentinel node.
9104/// The inverse target owner supplies only the exact class identity to prove;
9105/// lexical AST resolution still decides whether the alias denotes that class.
9106fn recovered_receiver_alias_target(
9107    reference: Node<'_>,
9108    alias: &CodeUnit,
9109    ctx: &ScanCtx<'_>,
9110) -> Option<CodeUnit> {
9111    if !ctx
9112        .analyzer
9113        .type_alias_provider()
9114        .is_some_and(|provider| provider.is_type_alias(alias))
9115    {
9116        return None;
9117    }
9118    let target = ctx.spec.owner.as_ref()?.clone();
9119    if !target.is_class() || alias.source() != ctx.file {
9120        return None;
9121    }
9122    let range = ctx
9123        .analyzer
9124        .ranges(alias)
9125        .into_iter()
9126        .find(|range| range.start_byte < range.end_byte)?;
9127    let mut node =
9128        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9129    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
9130        node = ctx.ancestry.parent(node)?;
9131    }
9132    let type_descriptor = node.child_by_field_name("type")?;
9133    let type_node = receiver_type_node_base(type_descriptor);
9134    let resolution = resolve_type_node_lexically_for_target(
9135        type_node,
9136        &ctx.analyzer,
9137        ctx.visibility,
9138        &ctx.ordinary_type_imports,
9139        ctx.file,
9140        ctx.source,
9141        &target,
9142        Some(&ctx.lexical_scope_cache),
9143        ctx.recovered_sentinel_scope(type_node).as_deref(),
9144    );
9145    if let LexicalTypeResolution::Resolved {
9146        unit, candidates, ..
9147    } = resolution
9148        && (same_visible_symbol(&unit, &target)
9149            || candidates
9150                .iter()
9151                .any(|candidate| same_visible_symbol(candidate, &target)))
9152    {
9153        return Some(target);
9154    }
9155    let (components, global) = type_reference_components(type_node, ctx.source)?;
9156    if !global
9157        && components.len() == 2
9158        && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
9159        && dependent_nested_alias_concrete_target(
9160            &ctx.analyzer,
9161            ctx.visibility,
9162            ctx.file,
9163            alias,
9164            &components[1],
9165            type_node,
9166        )
9167        .is_some_and(|unit| same_visible_symbol(&unit, &target))
9168    {
9169        return Some(target);
9170    }
9171    let scope = ctx
9172        .recovered_sentinel_scope(type_node)
9173        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9174    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
9175    let visible = ctx.visibility.external_type_candidate_visible_in_context(
9176        &ctx.analyzer,
9177        ctx.file,
9178        &target,
9179        type_node,
9180    );
9181    (path_matches && visible).then_some(target)
9182}
9183
9184/// The one concrete class that `alias`, spelled `Param::nested_name` through an
9185/// active template type parameter `Param`, can name: every other visible
9186/// class-owned alias named `nested_name` must reach the same indexed class
9187/// (#2250). `type_node` is the alias's target type node, whose guard context
9188/// each candidate must be compatible with. Competing concrete targets answer
9189/// `None`.
9190pub fn dependent_nested_alias_concrete_target(
9191    analyzer: &CppGraphSource<'_>,
9192    visibility: &VisibilityIndex<'_>,
9193    file: &ProjectFile,
9194    alias: &CodeUnit,
9195    nested_name: &str,
9196    type_node: Node<'_>,
9197) -> Option<CodeUnit> {
9198    let alias_provider = analyzer.type_alias_provider()?;
9199    let concrete = visibility
9200        .visible_identifier_candidates(file, nested_name)
9201        .filter(|candidate| {
9202            alias_provider.is_type_alias(candidate)
9203                && !same_visible_symbol(candidate, alias)
9204                && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
9205                && visibility.is_physically_visible(file, candidate)
9206                && visibility.external_type_candidate_guard_compatible_in_context(
9207                    analyzer, file, candidate, type_node,
9208                )
9209        })
9210        .filter_map(|candidate| {
9211            let canonical =
9212                visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
9213            // Another dependent alias can have the same nested name but
9214            // still canonicalize only to itself. It supplies no concrete
9215            // receiver identity and therefore cannot compete with an
9216            // alias that reaches an indexed class.
9217            (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
9218        })
9219        .collect::<Vec<_>>();
9220    match unanimous_receiver_units(concrete).as_slice() {
9221        [unit] => Some(unit.clone()),
9222        _ => None,
9223    }
9224}
9225
9226fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9227    let normalized = normalize_cpp_type_name(type_name);
9228    if normalized.is_empty() {
9229        return None;
9230    }
9231
9232    // A function-local alias is intentionally absent from the visibility
9233    // index. Recover its RHS from the structured alias declaration before
9234    // trying file-visible type lookup; this keeps the alias's lexical shadow
9235    // boundary intact.
9236    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
9237        match resolve_receiver_type_node(alias_type, ctx) {
9238            Ok(Some(unit)) => return Some(unit),
9239            Err(_) => return None,
9240            Ok(None) => {}
9241        }
9242    }
9243
9244    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
9245        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
9246        LexicalTypeResolution::Ambiguous => return None,
9247        LexicalTypeResolution::Missing => {}
9248    }
9249    let candidates = ctx
9250        .visibility
9251        .type_name_candidates(ctx.file, &normalized)
9252        .into_iter()
9253        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
9254        .collect();
9255    unanimous_receiver_units(candidates).into_iter().next()
9256}
9257
9258/// The receiver type a type node names: the visible indexed type it resolves
9259/// to, else the type its components name in the enclosing lexical scope.
9260///
9261/// A `type_descriptor` (a cast's type, an alias declaration's right-hand side)
9262/// is unwrapped to its type specifier first, so every caller that has a spelled
9263/// type reads it the same way. `Err` reports a template whose arguments could
9264/// not be resolved; that is a structured failure, not an absent type.
9265fn resolve_receiver_type_node(
9266    type_node: Node<'_>,
9267    ctx: &ScanCtx<'_>,
9268) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
9269    let type_node = receiver_type_node_base(type_node);
9270    if let Some(local) = function_local_type_for_node(type_node, ctx) {
9271        return Ok(local);
9272    }
9273    if let Some(unit) = ctx
9274        .visibility
9275        .resolve_type_node_result(ctx.file, type_node, ctx.source)?
9276    {
9277        return Ok(Some(unit));
9278    }
9279    Ok(resolve_receiver_type_node_lexically(type_node, ctx))
9280}
9281
9282fn resolve_receiver_type_node_lexically(
9283    type_node: Node<'_>,
9284    ctx: &ScanCtx<'_>,
9285) -> Option<CodeUnit> {
9286    let type_node = receiver_type_node_base(type_node);
9287    let components = cpp_type_name_components(type_node, ctx.source)?;
9288    let lexical_scope = match enclosing_lexical_scope_components(
9289        type_node,
9290        &ctx.analyzer,
9291        ctx.visibility,
9292        ctx.file,
9293        ctx.source,
9294    ) {
9295        LexicalScopeResolution::Resolved(scope) => scope,
9296        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
9297    };
9298    match ctx.visibility.resolve_type_components_lexically(
9299        &ctx.analyzer,
9300        ctx.file,
9301        &components,
9302        is_globally_qualified_cpp_name(type_node),
9303        &lexical_scope,
9304    ) {
9305        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
9306        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9307    }
9308}
9309
9310fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
9311    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
9312        let Some(inner) = node.child_by_field_name("type").or_else(|| {
9313            if node.kind() == "dependent_type" {
9314                node.named_child(0)
9315            } else {
9316                None
9317            }
9318        }) else {
9319            break;
9320        };
9321        node = inner;
9322    }
9323    node
9324}
9325
9326fn resolve_receiver_type_name_lexically(
9327    node: Node<'_>,
9328    normalized: &str,
9329    ctx: &ScanCtx<'_>,
9330) -> LexicalTypeResolution {
9331    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9332        brokk_bifrost_core::analyzer::Language::Cpp,
9333        normalized,
9334    );
9335    if components.is_empty() {
9336        return LexicalTypeResolution::Missing;
9337    }
9338    let lexical_scope = match enclosing_lexical_scope_components(
9339        node,
9340        &ctx.analyzer,
9341        ctx.visibility,
9342        ctx.file,
9343        ctx.source,
9344    ) {
9345        LexicalScopeResolution::Resolved(scope) => scope,
9346        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9347        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9348    };
9349    ctx.visibility.resolve_type_components_lexically(
9350        &ctx.analyzer,
9351        ctx.file,
9352        &components,
9353        normalized.starts_with("::"),
9354        &lexical_scope,
9355    )
9356}
9357
9358fn local_receiver_alias_type_node<'tree>(
9359    node: Node<'tree>,
9360    name: &str,
9361    ctx: &ScanCtx<'_>,
9362) -> Option<Node<'tree>> {
9363    let callable = nearest_callable_scope(node)?;
9364    let mut root_callable = callable;
9365    // See `target_guided_nested_type_terminal_hit`: the result carries the
9366    // caller's tree lifetime.
9367    let mut ancestor = callable.parent();
9368    while let Some(current) = ancestor {
9369        if matches!(current.kind(), "function_definition" | "lambda_expression") {
9370            root_callable = current;
9371        }
9372        ancestor = current.parent();
9373    }
9374
9375    let mut stack = vec![root_callable];
9376    let mut best = None;
9377    while let Some(current) = stack.pop() {
9378        if current.start_byte() >= node.start_byte() {
9379            continue;
9380        }
9381        if local_type_alias_name_node(current)
9382            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9383            && current
9384                .parent()
9385                .is_none_or(|parent| parent.kind() != "template_declaration")
9386            && has_function_scope_ancestor(current)
9387            && local_alias_scope_contains_node(current, node)
9388        {
9389            let replace = best
9390                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9391            if replace {
9392                best = current.child_by_field_name("type");
9393            }
9394        }
9395        let mut cursor = current.walk();
9396        stack.extend(current.named_children(&mut cursor));
9397    }
9398    best
9399}
9400
9401fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9402    let mut canonical = Vec::with_capacity(units.len());
9403    for unit in units {
9404        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9405            return Vec::new();
9406        };
9407        canonical.push(unit);
9408    }
9409    unanimous_receiver_units(canonical)
9410}
9411
9412fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9413    if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
9414        return cached.clone();
9415    }
9416    let canonical = ctx
9417        .visibility
9418        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9419    ctx.receiver_canonical_type_cache
9420        .borrow_mut()
9421        .insert(unit.clone(), canonical.clone());
9422    canonical
9423}
9424
9425fn receiver_units_from_declared_fields(
9426    fields: Vec<&CodeUnit>,
9427    reference: Node<'_>,
9428    ctx: &ScanCtx<'_>,
9429) -> Vec<CodeUnit> {
9430    let Some(first) = fields.first() else {
9431        return Vec::new();
9432    };
9433    if fields
9434        .iter()
9435        .skip(1)
9436        .any(|field| !same_visible_symbol(first, field))
9437    {
9438        return Vec::new();
9439    }
9440    unanimous_receiver_units(
9441        fields
9442            .into_iter()
9443            .filter_map(|field| {
9444                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9445                    .and_then(|binding| binding.unit)
9446                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9447            })
9448            .collect(),
9449    )
9450}
9451
9452/// Resolve a field receiver's declared type from its structured declaration
9453/// when the persisted type fact was built under a malformed sentinel scope.
9454/// The queried member owner supplies the exact class identity to prove; the
9455/// declaration's type node and recovered lexical path provide the evidence.
9456fn recovered_receiver_field_type(
9457    reference: Node<'_>,
9458    field: &CodeUnit,
9459    ctx: &ScanCtx<'_>,
9460) -> Option<CodeUnit> {
9461    let target = ctx.spec.owner.as_ref()?.clone();
9462    if !target.is_class() || field.source() != ctx.file {
9463        return None;
9464    }
9465    let range = ctx
9466        .analyzer
9467        .ranges(field)
9468        .into_iter()
9469        .find(|range| range.start_byte < range.end_byte)?;
9470    let mut declaration =
9471        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9472    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9473        declaration = ctx.ancestry.parent(declaration)?;
9474    }
9475    let type_node = first_type_child(declaration)?;
9476    let resolution = resolve_type_node_lexically_for_target(
9477        type_node,
9478        &ctx.analyzer,
9479        ctx.visibility,
9480        &ctx.ordinary_type_imports,
9481        ctx.file,
9482        ctx.source,
9483        &target,
9484        Some(&ctx.lexical_scope_cache),
9485        ctx.recovered_sentinel_scope(type_node).as_deref(),
9486    );
9487    if let LexicalTypeResolution::Resolved {
9488        unit, candidates, ..
9489    } = resolution
9490        && (same_visible_symbol(&unit, &target)
9491            || candidates
9492                .iter()
9493                .any(|candidate| same_visible_symbol(candidate, &target)))
9494    {
9495        return Some(target);
9496    }
9497    let type_node = receiver_type_node_base(type_node);
9498    let (components, global) = type_reference_components(type_node, ctx.source)?;
9499    let scope = ctx
9500        .recovered_sentinel_scope(type_node)
9501        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9502    (indexed_scope_matches_target_name(&scope, &components, global, &target)
9503        && ctx.visibility.external_type_candidate_visible_in_context(
9504            &ctx.analyzer,
9505            ctx.file,
9506            &target,
9507            type_node,
9508        ))
9509    .then_some(target)
9510}
9511
9512fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9513    let mut unique = Vec::new();
9514    for unit in units {
9515        if !unique
9516            .iter()
9517            .any(|existing| same_visible_symbol(existing, &unit))
9518        {
9519            unique.push(unit);
9520            if unique.len() > 1 {
9521                return Vec::new();
9522            }
9523        }
9524    }
9525    unique
9526}
9527
9528fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9529    let Some(owner) = ctx.spec.owner.as_ref() else {
9530        return false;
9531    };
9532    match node.kind() {
9533        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
9534        // When that structured lookup fails, the base expression's owner is not the field's
9535        // type: treating `value_` as `Value` would misclassify
9536        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
9537        // with the `this` node itself, so failing closed for an unresolved field chain does not
9538        // discard genuine self calls.
9539        "field_expression" => false,
9540        "call_expression" => node
9541            .child_by_field_name("function")
9542            .is_some_and(|function| receiver_matches_target(function, ctx)),
9543        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9544            .child_by_field_name("argument")
9545            .or_else(|| node.named_child(0))
9546            .is_some_and(|child| receiver_matches_target(child, ctx)),
9547        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9548            .bindings
9549            .resolve_symbol(node_text(node, ctx.source))
9550            .as_precise()
9551            .is_some_and(|targets| {
9552                targets
9553                    .iter()
9554                    .filter_map(|target| target.unit.as_ref())
9555                    .any(|target| {
9556                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9557                    })
9558            }),
9559        "this" => same_owner_context(node, ctx),
9560        _ => qualified_owner_matches(node, ctx),
9561    }
9562}
9563
9564fn declaring_owner_for_explicit_receiver(
9565    receiver: Node<'_>,
9566    call_arity: Option<usize>,
9567    ctx: &ScanCtx<'_>,
9568) -> EnclosingMemberOwnerResolution {
9569    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9570        return EnclosingMemberOwnerResolution::Missing;
9571    }
9572    declaring_owner_from_receiver_units(
9573        receiver_type_units(receiver, ctx.source, ctx),
9574        receiver.start_byte(),
9575        call_arity,
9576        ctx,
9577    )
9578}
9579
9580/// The owner that declares the queried member for a receiver already typed.
9581///
9582/// `reference_byte` is the reference's position in the scanned file, which
9583/// decides which declarations are visible there. A receiver recovered from a
9584/// macro replacement is typed against the sentinel parse but still reported at
9585/// its bytes in the defining file, so the two arrive separately.
9586fn declaring_owner_from_receiver_units(
9587    receiver_units: Vec<CodeUnit>,
9588    reference_byte: usize,
9589    call_arity: Option<usize>,
9590    ctx: &ScanCtx<'_>,
9591) -> EnclosingMemberOwnerResolution {
9592    let mut declaring_owner = None;
9593    for receiver_owner in receiver_units {
9594        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9595            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9596        }) {
9597            if declaring_owner
9598                .as_ref()
9599                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9600            {
9601                return EnclosingMemberOwnerResolution::Ambiguous;
9602            }
9603            declaring_owner = Some(receiver_owner);
9604            continue;
9605        }
9606        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9607        let owner_resolution = match call_arity {
9608            Some(arity) => resolve_declaring_callable_owner(
9609                &ctx.analyzer,
9610                ctx.visibility,
9611                ctx.file,
9612                ordinary,
9613                &ctx.spec.member_name,
9614                arity,
9615            ),
9616            None => ordinary,
9617        };
9618        match owner_resolution {
9619            EnclosingMemberOwnerResolution::Owner(owner) => {
9620                if declaring_owner
9621                    .as_ref()
9622                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9623                {
9624                    return EnclosingMemberOwnerResolution::Ambiguous;
9625                }
9626                declaring_owner = Some(owner);
9627            }
9628            EnclosingMemberOwnerResolution::Ambiguous => {
9629                return EnclosingMemberOwnerResolution::Ambiguous;
9630            }
9631            EnclosingMemberOwnerResolution::Missing => {}
9632        }
9633    }
9634    declaring_owner
9635        .map(EnclosingMemberOwnerResolution::Owner)
9636        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9637}
9638
9639fn declaring_owner_from_call_function(
9640    function: Node<'_>,
9641    call_arity: Option<usize>,
9642    ctx: &ScanCtx<'_>,
9643) -> Option<EnclosingMemberOwnerResolution> {
9644    match function.kind() {
9645        "field_expression" => function
9646            .child_by_field_name("argument")
9647            .or_else(|| function.child_by_field_name("object"))
9648            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9649            .or(Some(EnclosingMemberOwnerResolution::Missing)),
9650        "call_expression" => function
9651            .child_by_field_name("function")
9652            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9653        _ => None,
9654    }
9655}
9656
9657enum MethodReceiverTargetResolution {
9658    Target,
9659    NonTarget,
9660    Ambiguous,
9661    Missing,
9662}
9663
9664fn method_receiver_target_resolution(
9665    node: Node<'_>,
9666    declaring_owner: EnclosingMemberOwnerResolution,
9667    ctx: &ScanCtx<'_>,
9668) -> MethodReceiverTargetResolution {
9669    match declaring_owner {
9670        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9671            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9672        }
9673        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9674            MethodReceiverTargetResolution::Missing
9675        }
9676        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9677            MethodReceiverTargetResolution::Target
9678        }
9679        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9680            MethodReceiverTargetResolution::NonTarget
9681        }
9682        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9683    }
9684}
9685
9686/// Decide the queried member against a receiver's declaring owner alone.
9687///
9688/// This is the part of receiver typing that needs nothing but the owner and
9689/// the reference's position, so a member call recovered from a macro
9690/// replacement reaches the same verdict as ordinary code even though its
9691/// syntax lives in a separate sentinel parse.
9692fn declaring_owner_target_resolution(
9693    declaring_owner: EnclosingMemberOwnerResolution,
9694    reference_byte: usize,
9695    ctx: &ScanCtx<'_>,
9696) -> MethodReceiverTargetResolution {
9697    let Some(target_owner) = ctx.spec.owner.as_ref() else {
9698        return MethodReceiverTargetResolution::Missing;
9699    };
9700    match declaring_owner {
9701        EnclosingMemberOwnerResolution::Owner(owner)
9702            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9703        {
9704            MethodReceiverTargetResolution::Target
9705        }
9706        EnclosingMemberOwnerResolution::Owner(owner)
9707            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9708        {
9709            MethodReceiverTargetResolution::NonTarget
9710        }
9711        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9712            MethodReceiverTargetResolution::Missing
9713        }
9714        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9715    }
9716}
9717
9718fn explicit_receiver_target_resolution(
9719    receiver: Node<'_>,
9720    call_arity: Option<usize>,
9721    ctx: &ScanCtx<'_>,
9722) -> MethodReceiverTargetResolution {
9723    method_receiver_target_resolution(
9724        receiver,
9725        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9726        ctx,
9727    )
9728}
9729
9730fn call_function_target_resolution(
9731    function: Node<'_>,
9732    ctx: &ScanCtx<'_>,
9733) -> MethodReceiverTargetResolution {
9734    let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9735        (call.kind() == "call_expression")
9736            .then(|| {
9737                ctx.visibility
9738                    .call_arity_evidence(ctx.file, call, ctx.source)
9739                    .exact()
9740            })
9741            .flatten()
9742    });
9743    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9744    else {
9745        // A bare function identifier has an implicit receiver. Do not reinterpret
9746        // that identifier as a same-named type or value before enclosing-owner
9747        // lookup gets a chance to establish the member call.
9748        return MethodReceiverTargetResolution::Missing;
9749    };
9750    method_receiver_target_resolution(function, declaring_owner, ctx)
9751}
9752
9753fn receiver_owner_matches_target(
9754    receiver_owner: &CodeUnit,
9755    target_owner: &CodeUnit,
9756    reference_byte: usize,
9757    ctx: &ScanCtx<'_>,
9758) -> bool {
9759    same_symbol(receiver_owner, target_owner)
9760        || same_logical_symbol(receiver_owner, target_owner)
9761            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9762                || (ctx.spec.owner_is_forward_declaration
9763                    && ctx
9764                        .visibility
9765                        .is_physically_visible(ctx.file, receiver_owner))
9766                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9767                || target_group_contains_owner_peer(receiver_owner, ctx)
9768                // An identical definition of the class in a header this file
9769                // includes is the target's owner too, the relation forward
9770                // definition lookup crosses from the body's side (#6902).
9771                || ctx.visibility.same_logical_class_definition(
9772                    &ctx.analyzer,
9773                    receiver_owner,
9774                    target_owner,
9775                ))
9776}
9777
9778fn receiver_owner_is_known_non_target(
9779    receiver_owner: &CodeUnit,
9780    target_owner: &CodeUnit,
9781    reference_byte: usize,
9782    ctx: &ScanCtx<'_>,
9783) -> bool {
9784    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9785        return false;
9786    }
9787    if !same_logical_symbol(receiver_owner, target_owner) {
9788        return true;
9789    }
9790    !ctx.target_group.iter().any(|target| {
9791        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9792    })
9793}
9794
9795fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9796    ctx.visibility
9797        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9798        && ctx.target_group.iter().any(|target| {
9799            type_owner_of(&ctx.analyzer, target)
9800                .as_ref()
9801                .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9802        })
9803}
9804
9805fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9806    same_symbol(candidate_owner, owner)
9807        || (same_logical_symbol(candidate_owner, owner)
9808            && candidate_owner.source() == owner.source())
9809}
9810
9811fn visible_target_peer_matches_owner(
9812    owner: &CodeUnit,
9813    reference_byte: usize,
9814    ctx: &ScanCtx<'_>,
9815) -> bool {
9816    ctx.visibility
9817        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9818        && ctx
9819            .visibility
9820            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9821            .any(|candidate| {
9822                cpp_callable_definitions_share_identity_evidence(
9823                    &ctx.analyzer,
9824                    candidate,
9825                    &ctx.spec.target,
9826                ) && ctx.visibility.declaration_visible_at(
9827                    &ctx.analyzer,
9828                    ctx.file,
9829                    candidate,
9830                    reference_byte,
9831                ) && type_owner_of(&ctx.analyzer, candidate)
9832                    .as_ref()
9833                    .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9834            })
9835}
9836
9837fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9838    match function.kind() {
9839        "field_expression" => function
9840            .child_by_field_name("argument")
9841            .or_else(|| function.child_by_field_name("object"))
9842            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9843        _ => receiver_is_self_like(function, reference_is_c),
9844    }
9845}
9846
9847fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9848    let Some(owner) = ctx.spec.owner.as_ref() else {
9849        return false;
9850    };
9851    match node.kind() {
9852        "field_expression" => node
9853            .child_by_field_name("argument")
9854            .or_else(|| node.child_by_field_name("object"))
9855            .is_some_and(|receiver| {
9856                // `this` names the enclosing class, not necessarily the class that
9857                // declares the invoked member. Let the hierarchy-aware enclosing-owner
9858                // lookup decide whether this is a same-owner call, an inherited base
9859                // member, a nearer override, or an ambiguous base. Rejecting a derived
9860                // `this` merely because its immediate type differs from the target owner
9861                // drops genuine inherited calls (#2541).
9862                if receiver_is_self_like(
9863                    receiver,
9864                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
9865                ) {
9866                    return false;
9867                }
9868                if receiver_is_known_function_declarator(receiver, ctx) {
9869                    return true;
9870                }
9871                let units = receiver_type_units(receiver, ctx.source, ctx);
9872                !units.is_empty()
9873                    && units.iter().all(|target| {
9874                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9875                    })
9876            }),
9877        "call_expression" => node
9878            .child_by_field_name("function")
9879            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9880        "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9881            .child_by_field_name("argument")
9882            .or_else(|| node.named_child(0))
9883            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9884        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9885            .bindings
9886            .resolve_symbol(node_text(node, ctx.source))
9887            .as_precise()
9888            .is_some_and(|targets| {
9889                let units = targets
9890                    .iter()
9891                    .filter_map(|target| target.unit.as_ref())
9892                    .collect::<Vec<_>>();
9893                !units.is_empty()
9894                    && units.iter().all(|target| {
9895                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9896                    })
9897            }),
9898        "this" => known_non_target_owner_context(node, ctx),
9899        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9900            qualified_owner_is_known_non_target(node, ctx)
9901        }
9902        _ => false,
9903    }
9904}
9905
9906fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9907    if receiver.kind() != "identifier" {
9908        return false;
9909    }
9910    let receiver_name = node_text(receiver, ctx.source);
9911    let mut current = receiver;
9912    while let Some(parent) = ctx.ancestry.parent(current) {
9913        if parent.kind() == "compound_statement" {
9914            let mut cursor = parent.walk();
9915            for statement in parent.named_children(&mut cursor) {
9916                if statement.start_byte() >= receiver.start_byte() {
9917                    break;
9918                }
9919                if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9920                {
9921                    continue;
9922                }
9923                let type_text = statement
9924                    .child_by_field_name("type")
9925                    .or_else(|| first_type_child(statement))
9926                    .map(|type_node| node_text(type_node, ctx.source));
9927                let mut declaration_cursor = statement.walk();
9928                for child in statement.named_children(&mut declaration_cursor) {
9929                    let declarator = if child.kind() == "init_declarator" {
9930                        child.child_by_field_name("declarator")
9931                    } else if is_declarator_node(child) {
9932                        Some(child)
9933                    } else {
9934                        None
9935                    };
9936                    let Some(declarator) = declarator else {
9937                        continue;
9938                    };
9939                    if declarator.kind() == "function_declarator"
9940                        && extract_variable_name(declarator, ctx.source)
9941                            .is_some_and(|name| name == receiver_name)
9942                        && function_declarator_parameters_have_known_types(declarator, ctx)
9943                        && !constructor_style_local_declaration(
9944                            ctx.visibility,
9945                            ctx.file,
9946                            ctx.source,
9947                            declarator,
9948                            type_text,
9949                            &ctx.bindings,
9950                        )
9951                    {
9952                        return true;
9953                    }
9954                }
9955            }
9956            return false;
9957        }
9958        current = parent;
9959    }
9960    false
9961}
9962
9963fn function_declarator_parameters_have_known_types(
9964    declarator: Node<'_>,
9965    ctx: &ScanCtx<'_>,
9966) -> bool {
9967    let Some(parameters) = declarator.child_by_field_name("parameters") else {
9968        return false;
9969    };
9970    let mut cursor = parameters.walk();
9971    parameters.named_children(&mut cursor).all(|parameter| {
9972        if !matches!(
9973            parameter.kind(),
9974            "parameter_declaration" | "optional_parameter_declaration"
9975        ) {
9976            return false;
9977        }
9978        let Some(type_node) = parameter
9979            .child_by_field_name("type")
9980            .or_else(|| first_type_child(parameter))
9981        else {
9982            return false;
9983        };
9984        type_node.kind() == "primitive_type"
9985            || ctx
9986                .visibility
9987                .resolve_type_node_result(ctx.file, type_node, ctx.source)
9988                .ok()
9989                .flatten()
9990                .is_some()
9991    })
9992}
9993
9994#[derive(Clone, Copy, PartialEq, Eq)]
9995enum QualifiedOwnerResolution {
9996    Target,
9997    NonTarget,
9998    Unresolved,
9999}
10000
10001#[derive(Clone)]
10002pub enum LexicalScopeResolution {
10003    Resolved(Vec<String>),
10004    Ambiguous,
10005    Missing,
10006}
10007
10008/// One scan's memo of lexical scope reconstructions for one file, keyed by
10009/// the reconstruction's anchor range and flags, with the file's recovered
10010/// namespace regions that the key and the reconstruction both read.
10011pub struct LexicalScopeCache {
10012    resolutions: RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>,
10013    orphaned: Arc<OrphanedNamespaceScopeIndex>,
10014}
10015
10016impl LexicalScopeCache {
10017    fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
10018        Self {
10019            resolutions: RefCell::new(HashMap::default()),
10020            orphaned: orphaned_namespace_scopes(visibility, file),
10021        }
10022    }
10023}
10024
10025/// The namespaces parse recovery dropped from `file`, memoized on the
10026/// analyzer (issue #1537).
10027fn orphaned_namespace_scopes(
10028    visibility: &VisibilityIndex<'_>,
10029    file: &ProjectFile,
10030) -> Arc<OrphanedNamespaceScopeIndex> {
10031    visibility
10032        .cpp()
10033        .orphaned_namespace_scopes(visibility.token(), file)
10034}
10035
10036fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10037    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
10038}
10039
10040fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10041    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
10042}
10043
10044fn is_structurally_qualified(node: Node<'_>) -> bool {
10045    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10046        && qualified_name_has_concrete_scope_separators(node)
10047}
10048
10049fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
10050    let Some(target_owner) = ctx.spec.owner.as_ref() else {
10051        return QualifiedOwnerResolution::Unresolved;
10052    };
10053    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
10054        return QualifiedOwnerResolution::Unresolved;
10055    };
10056    // A malformed wrapper can make the parser-derived enclosing owner look
10057    // like the lexical namespace (for example tinyxml2's macro-prefixed
10058    // XMLHandle declarations). Recover the target namespace only while this
10059    // function owns scope reconstruction. An explicit recovered scope is
10060    // authoritative and enters the scoped resolver directly.
10061    if !global
10062        && !matches!(
10063            enclosing_lexical_scope_components(
10064                node,
10065                &ctx.analyzer,
10066                ctx.visibility,
10067                ctx.file,
10068                ctx.source,
10069            ),
10070            LexicalScopeResolution::Resolved(_)
10071        )
10072    {
10073        return QualifiedOwnerResolution::Unresolved;
10074    }
10075    match resolve_type_components_lexically_at_for_target_with_scope_cache(
10076        node,
10077        &components,
10078        global,
10079        &ctx.analyzer,
10080        ctx.visibility,
10081        &ctx.ordinary_type_imports,
10082        ctx.file,
10083        ctx.source,
10084        target_owner,
10085        false,
10086        Some(&ctx.lexical_scope_cache),
10087    ) {
10088        LexicalTypeResolution::Resolved { unit: owner, .. } => {
10089            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
10090                return QualifiedOwnerResolution::Target;
10091            }
10092            match cached_declaring_member_owner(&owner, ctx) {
10093                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10094                    if receiver_owner_matches_target(
10095                        &declaring_owner,
10096                        target_owner,
10097                        node.start_byte(),
10098                        ctx,
10099                    ) =>
10100                {
10101                    QualifiedOwnerResolution::Target
10102                }
10103                EnclosingMemberOwnerResolution::Owner(declaring_owner)
10104                    if receiver_owner_is_known_non_target(
10105                        &declaring_owner,
10106                        target_owner,
10107                        node.start_byte(),
10108                        ctx,
10109                    ) =>
10110                {
10111                    QualifiedOwnerResolution::NonTarget
10112                }
10113                EnclosingMemberOwnerResolution::Owner(_)
10114                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
10115                EnclosingMemberOwnerResolution::Missing
10116                    if same_visible_symbol(&owner, target_owner) =>
10117                {
10118                    QualifiedOwnerResolution::Unresolved
10119                }
10120                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
10121            }
10122        }
10123        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
10124            QualifiedOwnerResolution::Unresolved
10125        }
10126    }
10127}
10128
10129fn qualified_callable_owner_components(
10130    node: Node<'_>,
10131    source: &str,
10132) -> Option<(Vec<String>, bool)> {
10133    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10134        || !qualified_name_has_concrete_scope_separators(node)
10135    {
10136        return None;
10137    }
10138    let owner = qualified_owner_components(node, source)?;
10139    Some((owner.names, owner.global))
10140}
10141
10142fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
10143    if !matches!(
10144        node.kind(),
10145        "identifier"
10146            | "type_identifier"
10147            | "namespace_identifier"
10148            | "qualified_identifier"
10149            | "scoped_identifier"
10150            | "scoped_type_identifier"
10151            | "template_type"
10152            | "template_function"
10153    ) {
10154        return None;
10155    }
10156    let components = cpp_type_name_components(node, source)?;
10157    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
10158}
10159
10160/// The block-scope answer for a type node, when a local class declaration
10161/// decides it: `Some(None)` when a block declares the name but it denotes no
10162/// minted class, so callers must not fall back to an outer type.
10163fn function_local_type_for_node(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<Option<CodeUnit>> {
10164    let (components, global) = type_reference_components(node, ctx.source)?;
10165    if global {
10166        return None;
10167    }
10168    match function_local_type_resolution(&ctx.analyzer, ctx.file, node, &components, ctx.source)? {
10169        LexicalTypeResolution::Resolved { unit, .. } => Some(Some(unit)),
10170        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => Some(None),
10171    }
10172}
10173
10174pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
10175    let mut namespaces = Vec::new();
10176    for parent in enclosing_cpp_namespace_nodes(node) {
10177        if let Some(name) = parent.child_by_field_name("name") {
10178            let mut components = Vec::new();
10179            if append_cpp_name_components(name, source, &mut components).is_some() {
10180                namespaces.push(components);
10181            }
10182        }
10183    }
10184    namespaces.into_iter().flatten().collect()
10185}
10186
10187pub fn enclosing_lexical_scope_components(
10188    node: Node<'_>,
10189    analyzer: &CppGraphSource<'_>,
10190    visibility: &VisibilityIndex<'_>,
10191    file: &ProjectFile,
10192    source: &str,
10193) -> LexicalScopeResolution {
10194    enclosing_lexical_scope_components_with_unresolved_owner(
10195        node,
10196        analyzer,
10197        visibility,
10198        file,
10199        source,
10200        false,
10201        false,
10202        &orphaned_namespace_scopes(visibility, file),
10203    )
10204}
10205
10206#[allow(clippy::too_many_arguments)]
10207fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
10208    node: Node<'_>,
10209    analyzer: &CppGraphSource<'_>,
10210    visibility: &VisibilityIndex<'_>,
10211    file: &ProjectFile,
10212    source: &str,
10213    allow_structured_unresolved_owner: bool,
10214    ignore_function_owner: bool,
10215    cache: Option<&LexicalScopeCache>,
10216) -> LexicalScopeResolution {
10217    let Some(cache) = cache else {
10218        return enclosing_lexical_scope_components_with_unresolved_owner(
10219            node,
10220            analyzer,
10221            visibility,
10222            file,
10223            source,
10224            allow_structured_unresolved_owner,
10225            ignore_function_owner,
10226            &orphaned_namespace_scopes(visibility, file),
10227        );
10228    };
10229    let orphaned = &cache.orphaned;
10230    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
10231    // A namespace body or translation unit anchor spans every recovered
10232    // region among its children, whose scopes differ; the region bounds keep
10233    // the key exact there and leave an anchor inside one region unchanged.
10234    let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
10235        Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
10236        None => (anchor_start, anchor_end),
10237    };
10238    let key = (
10239        anchor_start,
10240        anchor_end,
10241        allow_structured_unresolved_owner,
10242        ignore_function_owner,
10243    );
10244    if let Some(cached) = cache.resolutions.borrow().get(&key).cloned() {
10245        return cached;
10246    }
10247    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
10248        node,
10249        analyzer,
10250        visibility,
10251        file,
10252        source,
10253        allow_structured_unresolved_owner,
10254        ignore_function_owner,
10255        orphaned,
10256    );
10257    cache.resolutions.borrow_mut().insert(key, resolved.clone());
10258    resolved
10259}
10260
10261fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
10262    let mut current = node;
10263    loop {
10264        if matches!(
10265            current.kind(),
10266            "function_definition"
10267                | "class_specifier"
10268                | "struct_specifier"
10269                | "union_specifier"
10270                | "namespace_definition"
10271                | "translation_unit"
10272        ) {
10273            return (current.start_byte(), current.end_byte());
10274        }
10275        let Some(parent) = current.parent() else {
10276            return (current.start_byte(), current.end_byte());
10277        };
10278        current = parent;
10279    }
10280}
10281
10282#[allow(clippy::too_many_arguments)]
10283fn enclosing_lexical_scope_components_with_unresolved_owner(
10284    node: Node<'_>,
10285    analyzer: &CppGraphSource<'_>,
10286    visibility: &VisibilityIndex<'_>,
10287    file: &ProjectFile,
10288    source: &str,
10289    allow_structured_unresolved_owner: bool,
10290    ignore_function_owner: bool,
10291    orphaned: &OrphanedNamespaceScopeIndex,
10292) -> LexicalScopeResolution {
10293    #[cfg(any(test, feature = "test-support"))]
10294    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
10295    // One ancestor climb collects the namespace chain, the class chain, the
10296    // nearest function definition and both displaced-class-shape facts.
10297    // `Node::parent` re-descends from the root on every call (tree-sitter
10298    // 0.24+), so the four separate climbs this replaces each cost another
10299    // near-full-AST scan per reconstruction on a large flat file (#1927).
10300    let mut namespaces = Vec::new();
10301    let mut classes = Vec::new();
10302    let mut function_definition = None;
10303    let mut displaced_class_scope = false;
10304    let mut current = node.parent();
10305    while let Some(parent) = current {
10306        match parent.kind() {
10307            "namespace_definition" => {
10308                if let Some(name) = parent.child_by_field_name("name") {
10309                    let mut components = Vec::new();
10310                    if append_cpp_name_components(name, source, &mut components).is_some() {
10311                        namespaces.push((
10312                            parent.start_byte(),
10313                            components,
10314                            namespace_definition_has_real_braces(parent),
10315                        ));
10316                    }
10317                }
10318            }
10319            "class_specifier" | "struct_specifier" | "union_specifier" => {
10320                if let Some(name) = parent.child_by_field_name("name") {
10321                    let mut components = Vec::new();
10322                    if append_cpp_name_components(name, source, &mut components).is_some() {
10323                        classes.push(components);
10324                    }
10325                }
10326            }
10327            "function_definition" => {
10328                if function_definition.is_none() {
10329                    function_definition = Some(parent);
10330                }
10331                displaced_class_scope = displaced_class_scope
10332                    || parent.child_by_field_name("type").is_some_and(|type_node| {
10333                        matches!(
10334                            type_node.kind(),
10335                            "class_specifier" | "struct_specifier" | "union_specifier"
10336                        )
10337                    })
10338                    || is_malformed_wrapper_function_definition(parent);
10339            }
10340            _ => {}
10341        }
10342        current = parent.parent();
10343    }
10344    namespaces.reverse();
10345    // The parsed chain can stop short of the namespaces tree-sitter's error
10346    // recovery closed early; the orphaned index restores them (#1537).
10347    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
10348    let mut scope = namespace.clone();
10349    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
10350    // qualified function declarator. It is recovery scaffolding, not a real
10351    // callable owner, and must not overwrite the indexed class scope retained
10352    // by the wrapper body (#2249).
10353    let has_qualified_function_owner = function_definition
10354        .filter(|function| !is_malformed_wrapper_function_definition(*function))
10355        .and_then(function_definition_owner_lookup_node)
10356        .is_some_and(|owner| {
10357            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10358        });
10359    let indexed_scope = displaced_class_scope
10360        .then(|| {
10361            indexed_structural_class_scope(visibility, file, node, source)
10362                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10363        })
10364        .flatten()
10365        .or_else(|| {
10366            // A qualified out-of-line definition can lose its class owner from
10367            // the parser tree when a namespace sentinel or export macro wraps
10368            // the declaration.  Recover the indexed owner scope up front so
10369            // all unqualified type references in the body see the same class
10370            // boundary as C++ lookup, including aliases in parameters and
10371            // local declarations (not only template-argument leaves).
10372            (has_qualified_function_owner && function_definition.is_some())
10373                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10374                .flatten()
10375                .filter(|indexed| {
10376                    qualified_owner_scope_is_recoverable(
10377                        indexed,
10378                        &namespace,
10379                        &classes,
10380                        function_definition
10381                            .and_then(function_definition_owner_lookup_node)
10382                            .and_then(|owner| qualified_callable_owner_components(owner, source))
10383                            .map(|(components, _)| components),
10384                    )
10385                })
10386        })
10387        .or_else(|| {
10388            // A nested class declaration can likewise lose one of its outer
10389            // class ancestors from the CST.  Prefer the exact indexed
10390            // structural class scope. Its same-file declaration range and
10391            // exact template-id match already prove the owner, including a
10392            // specialization whose parser component retains only the primary
10393            // name. Keep the suffix guard for graph-only recovery, which lacks
10394            // that direct syntax-range proof.
10395            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10396                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10397                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10398                })
10399            })
10400        })
10401        .or_else(|| {
10402            // Retain the existing indexed lexical-scope recovery for
10403            // unqualified function bodies.  It is intentionally last so a
10404            // canonical class owner wins whenever one is available.
10405            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10406                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10407                .flatten()
10408                .filter(|indexed| indexed.len() > namespace.len())
10409        });
10410    if let Some(indexed_scope) = indexed_scope.as_ref() {
10411        // A macro-displaced namespace can leave the parser with the real class
10412        // body but no namespace ancestor. Prefer the structural class match;
10413        // partial specializations whose structured name cannot round-trip use
10414        // the exact indexed enclosing-owner chain instead.
10415        scope = indexed_scope.clone();
10416        classes.clear();
10417    }
10418
10419    if !ignore_function_owner
10420        && has_qualified_function_owner
10421        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10422    {
10423        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10424            return LexicalScopeResolution::Missing;
10425        };
10426        // Resolve the out-of-line owner from the parser namespace before the
10427        // provisional indexed parent can influence the answer. Per-file
10428        // extraction can assign the first same-depth using namespace to a
10429        // bare owner. The structured using resolver instead selects the
10430        // namespace whose visible class has the owner name (#1838).
10431        let imports = visibility.ordinary_type_import_cell(file);
10432        let owner_resolution = resolve_type_components_lexically_at_scoped(
10433            function,
10434            &owner,
10435            global,
10436            analyzer,
10437            visibility,
10438            &imports,
10439            file,
10440            source,
10441            None,
10442            false,
10443            false,
10444            false,
10445            namespace.clone(),
10446        );
10447        match owner_resolution {
10448            LexicalTypeResolution::Resolved {
10449                unit, components, ..
10450            } if is_indexed_class_owner(analyzer, &unit) => {
10451                scope = components;
10452                classes.clear();
10453            }
10454            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10455            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10456                match visibility
10457                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10458                {
10459                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
10460                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10461                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10462                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10463                            qualified_owner_scope_is_recoverable(
10464                                indexed,
10465                                &namespace,
10466                                &classes,
10467                                Some(owner.clone()),
10468                            )
10469                        }) {
10470                            scope = indexed.clone();
10471                        } else {
10472                            scope = if global || owner.starts_with(&namespace) {
10473                                owner
10474                            } else {
10475                                let mut relative = namespace;
10476                                relative.extend(owner);
10477                                relative
10478                            };
10479                        }
10480                    }
10481                    LexicalTypeResolution::Missing => {
10482                        // Structural lexical resolution cannot see an owner class that
10483                        // is reachable only through an in-scope `using namespace`
10484                        // directive, so it would otherwise hard-fail here. The indexed
10485                        // definition already carries the true fully-qualified owner
10486                        // (its package reflects the directive), so recover the real
10487                        // enclosing scope from the analyzer graph -- exactly the scope
10488                        // chain real C++ unqualified lookup traverses. Only the strict
10489                        // callers reach this arm; the best-effort callers above keep
10490                        // their existing structural guess (and its query profile).
10491                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10492                            .or_else(|| {
10493                                indexed_namespace_qualified_scope(
10494                                    analyzer, visibility, file, node, &owner,
10495                                )
10496                            }) {
10497                            Some(indexed) => scope = indexed,
10498                            None => return LexicalScopeResolution::Missing,
10499                        }
10500                    }
10501                }
10502            }
10503        }
10504    }
10505
10506    classes.reverse();
10507    scope.extend(classes.into_iter().flatten());
10508    LexicalScopeResolution::Resolved(scope)
10509}
10510
10511fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10512    let mut current = node.parent();
10513    while let Some(parent) = current {
10514        if parent.kind() == "function_definition"
10515            && parent.child_by_field_name("type").is_some_and(|type_node| {
10516                matches!(
10517                    type_node.kind(),
10518                    "class_specifier" | "struct_specifier" | "union_specifier"
10519                )
10520            })
10521        {
10522            return true;
10523        }
10524        current = parent.parent();
10525    }
10526    false
10527}
10528
10529fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10530    let mut current = node.parent();
10531    while let Some(parent) = current {
10532        if parent.kind() == "function_definition"
10533            && is_malformed_wrapper_function_definition(parent)
10534        {
10535            return true;
10536        }
10537        current = parent.parent();
10538    }
10539    false
10540}
10541
10542/// Whether a function definition is a recovery wrapper with a non-function
10543/// declarator. The older grammar's evidence is parser damage; the
10544/// macro-declaration grammar keeps the same envelope clean, where a
10545/// `macro_modifier`/`macro_modifier_declarator`/`macro_type_specifier` head is
10546/// admitted only where the parse would otherwise contain an error.
10547pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10548    let mut cursor = node.walk();
10549    let recovery_evidence = node.has_error()
10550        || node.named_children(&mut cursor).any(|child| {
10551            matches!(
10552                child.kind(),
10553                "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10554            )
10555        });
10556    recovery_evidence
10557        && node
10558            .child_by_field_name("declarator")
10559            .is_some_and(|declarator| {
10560                declarator.kind() != "function_declarator"
10561                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10562            })
10563}
10564
10565/// Tree-sitter can make an attribute/nullability macro look like the namespace
10566/// component of a qualified function owner when it appears between the return
10567/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10568/// The recovered owner is not a C++ lexical owner, so callers resolving the
10569/// ordinary return/parameter type must retain the surrounding namespace scope.
10570fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10571    node.child_by_field_name("scope")
10572        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10573        .is_some()
10574}
10575
10576fn indexed_structural_class_scope(
10577    visibility: &VisibilityIndex<'_>,
10578    file: &ProjectFile,
10579    node: Node<'_>,
10580    source: &str,
10581) -> Option<Vec<String>> {
10582    let mut current = node.parent();
10583    while let Some(parent) = current {
10584        if matches!(
10585            parent.kind(),
10586            "class_specifier" | "struct_specifier" | "union_specifier"
10587        ) {
10588            return visibility.indexed_structural_class_scope(file, parent, source);
10589        }
10590        current = parent.parent();
10591    }
10592    None
10593}
10594
10595/// Check that an indexed owner scope is a structured completion of the parser
10596/// scope rather than an unrelated same-spelled declaration.
10597///
10598/// Error recovery around C++ namespace sentinels can preserve only a subset of
10599/// the namespace/class chain.  The indexed definition still carries the full
10600/// owner path, so require every surviving parser component to occur in order
10601/// and require any explicit qualified function owner to be the terminal
10602/// suffix.  An empty parser scope is accepted only with that qualified-owner
10603/// suffix evidence; a lone top-level short name is not evidence that a
10604/// namespace was lost.
10605fn qualified_owner_scope_is_recoverable(
10606    indexed: &[String],
10607    namespace: &[String],
10608    classes: &[Vec<String>],
10609    qualified_owner: Option<Vec<String>>,
10610) -> bool {
10611    if let Some(owner) = qualified_owner {
10612        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10613            return false;
10614        }
10615        // A malformed namespace sentinel can erase every parser namespace
10616        // ancestor.  The indexed enclosing callable still provides an
10617        // authoritative class owner, so the qualified owner suffix itself is
10618        // enough evidence in that case.  When namespace components survived,
10619        // retain the stricter subsequence check below.
10620        if namespace.is_empty() {
10621            return true;
10622        }
10623        if indexed.len() <= namespace.len() {
10624            return false;
10625        }
10626        return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10627    }
10628    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10629    if !class_components.is_empty() {
10630        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10631    }
10632    if namespace.is_empty() || indexed.len() <= namespace.len() {
10633        return false;
10634    }
10635    super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10636}
10637
10638/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10639/// counts as the true lexical owner recovered from the indexed graph.
10640fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10641    unit.is_class()
10642        && !analyzer
10643            .type_alias_provider()
10644            .is_some_and(|provider| provider.is_type_alias(unit))
10645}
10646
10647/// Recover the enclosing member's true lexical scope from the *indexed*
10648/// definition when structural resolution cannot see the owner class.
10649///
10650/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10651/// {...}`) whose owner class is reachable only through an in-scope `using
10652/// namespace X;` directive cannot be resolved by `resolve_type_components_
10653/// lexically`, which walks structural lexical tiers and never consults
10654/// using-directives. The definition itself, however, is indexed with its true
10655/// fully-qualified identity (its package already reflects the directive), so
10656/// the analyzer graph knows the real owner. Walk from the reference's indexed
10657/// enclosing code unit up to the innermost enclosing class and return that
10658/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10659/// -- exactly the scope chain C++ unqualified lookup traverses.
10660fn indexed_enclosing_owner_scope(
10661    analyzer: &CppGraphSource<'_>,
10662    visibility: &VisibilityIndex<'_>,
10663    file: &ProjectFile,
10664    node: Node<'_>,
10665) -> Option<Vec<String>> {
10666    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10667}
10668
10669/// Recover the lexical scope of an out-of-line definition whose syntactic
10670/// qualifier names a *namespace* rather than a class (`void out::target(int)
10671/// {...}` written at file scope, issue #3096).
10672///
10673/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10674/// nothing here and the strict caller would fail closed for every unqualified
10675/// reference in the body. C++ unqualified lookup inside such a definition
10676/// proceeds from the named namespace outward, exactly as inside a `namespace
10677/// out { ... }` block, and the indexed definition already carries that scope
10678/// (`["out"]` for `out::target`).
10679///
10680/// Take it only when a namespace of that name is visible from the file and the
10681/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10682/// the graph does not hold (`void MissingContainer::call()`) has no such
10683/// namespace, so it still fails closed -- and it is rejected by the in-memory
10684/// visibility index, before any indexed enclosing-unit query.
10685fn indexed_namespace_qualified_scope(
10686    analyzer: &CppGraphSource<'_>,
10687    visibility: &VisibilityIndex<'_>,
10688    file: &ProjectFile,
10689    node: Node<'_>,
10690    owner: &[String],
10691) -> Option<Vec<String>> {
10692    let name = owner.last().expect("a qualified owner has one component");
10693    if !visibility
10694        .visible_identifier_candidates(file, name)
10695        .any(|unit| unit.is_module())
10696    {
10697        return None;
10698    }
10699    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10700    indexed.ends_with(owner).then_some(indexed)
10701}
10702
10703fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10704    let start = enclosing_context(node, ctx).enclosing?;
10705    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10706        ctx.analyzer.parent_of(unit)
10707    })
10708    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10709}
10710
10711/// How a qualified type's qualifier selects the owner of the member it names.
10712pub enum QualifierOwnerSelection {
10713    /// No qualifier applies template arguments or names an alias, or the
10714    /// arguments select the primary template, which the ordinary component
10715    /// lookup already names.
10716    Unchanged,
10717    /// The qualifier selects an owner the plain spelling does not reach: a
10718    /// specialization its arguments name, or the class an alias qualifier
10719    /// denotes. Look the rest of the path up through the owner's own scope
10720    /// components.
10721    Selected(Vec<String>),
10722    /// The arguments cannot be bound, more than one specialization applies,
10723    /// or the alias arms the reference admits disagree. The reference stays
10724    /// unproven rather than falling back to the primary template's member.
10725    Ambiguous,
10726}
10727
10728/// Select the owner a qualifier such as `Owner<int>` in `Owner<int>::Alias`
10729/// names before its member is looked up (#6902 item 9).
10730///
10731/// Component lookup reads a qualified name as plain spellings, so it drops
10732/// the qualifier's template arguments and always reaches the primary
10733/// template's member. The qualifier prefix is resolved lexically to its
10734/// primary template, and the shared argument binding and specialization
10735/// selection ([`VisibilityIndex::resolve_template_arguments`]) pick the class
10736/// those arguments name, the same choice an unqualified `Owner<int>` makes.
10737/// Only the last qualifier that applies arguments is selected; a deeper one,
10738/// as in `A<int>::B<char>::C`, keeps the ordinary lookup. A qualifier without
10739/// arguments may still be an alias of a specialization
10740/// ([`select_alias_qualifier_owner`]).
10741#[allow(clippy::too_many_arguments)]
10742pub fn select_qualifier_owner(
10743    node: Node<'_>,
10744    global: bool,
10745    analyzer: &CppGraphSource<'_>,
10746    visibility: &VisibilityIndex<'_>,
10747    ordinary_type_imports: &OrdinaryTypeImportCell,
10748    file: &ProjectFile,
10749    source: &str,
10750) -> QualifierOwnerSelection {
10751    if !matches!(
10752        node.kind(),
10753        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10754    ) {
10755        return QualifierOwnerSelection::Unchanged;
10756    }
10757    let Some(components) = cpp_name_component_nodes(node) else {
10758        return QualifierOwnerSelection::Unchanged;
10759    };
10760    fn templated(component: Node<'_>) -> Option<Node<'_>> {
10761        component.parent().filter(|parent| {
10762            parent.kind() == "template_type"
10763                && parent.child_by_field_name("name") == Some(component)
10764        })
10765    }
10766    let qualifiers = &components[..components.len().saturating_sub(1)];
10767    let Some((index, template)) = qualifiers
10768        .iter()
10769        .enumerate()
10770        .rev()
10771        .find_map(|(index, component)| templated(*component).map(|template| (index, template)))
10772    else {
10773        return select_alias_qualifier_owner(
10774            node,
10775            &components,
10776            global,
10777            analyzer,
10778            visibility,
10779            file,
10780            source,
10781        );
10782    };
10783    if qualifiers[..index]
10784        .iter()
10785        .any(|component| templated(*component).is_some())
10786    {
10787        return QualifierOwnerSelection::Unchanged;
10788    }
10789    let Some(arguments) = cpp_template_reference_arguments(template, source) else {
10790        return QualifierOwnerSelection::Unchanged;
10791    };
10792    let names = components
10793        .iter()
10794        .map(|component| node_text(*component, source).to_owned())
10795        .collect::<Vec<_>>();
10796    let primary = match resolve_type_components_lexically_at(
10797        node,
10798        &names[..=index],
10799        global,
10800        analyzer,
10801        visibility,
10802        ordinary_type_imports,
10803        file,
10804        source,
10805    ) {
10806        LexicalTypeResolution::Resolved { unit, .. } => unit,
10807        LexicalTypeResolution::Ambiguous => return QualifierOwnerSelection::Ambiguous,
10808        LexicalTypeResolution::Missing => return QualifierOwnerSelection::Unchanged,
10809    };
10810    match visibility.resolve_template_arguments(file, primary.clone(), &arguments) {
10811        Ok(owner) if same_visible_symbol(&owner, &primary) => QualifierOwnerSelection::Unchanged,
10812        Ok(owner) => {
10813            let mut redirected = canonical_cpp_scope_components(&owner);
10814            redirected.extend_from_slice(&names[index + 1..]);
10815            QualifierOwnerSelection::Selected(redirected)
10816        }
10817        Err(_) => QualifierOwnerSelection::Ambiguous,
10818    }
10819}
10820
10821/// The owner an alias qualifier such as `Selected` in `Selected::Alias`
10822/// denotes.
10823///
10824/// `using Selected = Owner<int>;` keeps the arguments in the alias's own
10825/// declaration, so the qualifier resolves through the alias to the selected
10826/// specialization, never to the primary template that the spelling `Owner`
10827/// would reach. The alias declarations the reference's own guards admit are
10828/// read directly: conditional arms that select different owners leave the
10829/// member undecided, and only arms that all agree decide it.
10830fn select_alias_qualifier_owner(
10831    node: Node<'_>,
10832    components: &[Node<'_>],
10833    global: bool,
10834    analyzer: &CppGraphSource<'_>,
10835    visibility: &VisibilityIndex<'_>,
10836    file: &ProjectFile,
10837    source: &str,
10838) -> QualifierOwnerSelection {
10839    let Some((terminal, qualifiers)) = components.split_last() else {
10840        return QualifierOwnerSelection::Unchanged;
10841    };
10842    let Some(last_qualifier) = qualifiers.last() else {
10843        return QualifierOwnerSelection::Unchanged;
10844    };
10845    let names = qualifiers
10846        .iter()
10847        .map(|component| node_text(*component, source).to_owned())
10848        .collect::<Vec<_>>();
10849    let declared = visibility
10850        .visible_identifier_candidates(file, node_text(*last_qualifier, source))
10851        .filter(|candidate| declared_type_alias(analyzer, candidate))
10852        .filter(|candidate| {
10853            let scope = canonical_cpp_scope_components(candidate);
10854            if global {
10855                scope == names
10856            } else {
10857                scope.ends_with(&names)
10858            }
10859        })
10860        .collect::<Vec<_>>();
10861    let aliases = declared
10862        .iter()
10863        .filter(|candidate| {
10864            visibility.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10865        })
10866        .map(|alias| visibility.canonical_type_unit_in_context(analyzer, file, node, alias))
10867        .collect::<Vec<_>>();
10868    let Some((first, rest)) = aliases.split_first() else {
10869        return QualifierOwnerSelection::Unchanged;
10870    };
10871    let Some(owner) = first else {
10872        return QualifierOwnerSelection::Unchanged;
10873    };
10874    if !rest.iter().all(|target| {
10875        target
10876            .as_ref()
10877            .is_some_and(|target| same_visible_symbol(target, owner))
10878    }) {
10879        return QualifierOwnerSelection::Ambiguous;
10880    }
10881    // Ordinary lookup already follows an unconditional alias to a class it
10882    // can name by spelling. It cannot name a specialization, and conditional
10883    // arms leave it undecided even when the arms the reference admits agree,
10884    // so the decided owner is used in those two cases only.
10885    let conditional = aliases.len() < declared.len();
10886    if !owner.is_class() || (!conditional && !visibility.is_template_specialization(owner)) {
10887        return QualifierOwnerSelection::Unchanged;
10888    }
10889    let mut redirected = canonical_cpp_scope_components(owner);
10890    redirected.push(node_text(*terminal, source).to_owned());
10891    QualifierOwnerSelection::Selected(redirected)
10892}
10893
10894pub fn resolve_type_node_lexically(
10895    node: Node<'_>,
10896    analyzer: &CppGraphSource<'_>,
10897    visibility: &VisibilityIndex<'_>,
10898    ordinary_type_imports: &OrdinaryTypeImportCell,
10899    file: &ProjectFile,
10900    source: &str,
10901) -> LexicalTypeResolution {
10902    let Some((components, global)) = type_reference_components(node, source) else {
10903        return LexicalTypeResolution::Missing;
10904    };
10905    match select_qualifier_owner(
10906        node,
10907        global,
10908        analyzer,
10909        visibility,
10910        ordinary_type_imports,
10911        file,
10912        source,
10913    ) {
10914        QualifierOwnerSelection::Unchanged => {}
10915        QualifierOwnerSelection::Selected(components) => {
10916            return resolve_type_components_lexically_at(
10917                node,
10918                &components,
10919                true,
10920                analyzer,
10921                visibility,
10922                ordinary_type_imports,
10923                file,
10924                source,
10925            );
10926        }
10927        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
10928    }
10929    let resolution = resolve_type_components_lexically_at(
10930        node,
10931        &components,
10932        global,
10933        analyzer,
10934        visibility,
10935        ordinary_type_imports,
10936        file,
10937        source,
10938    );
10939    if !is_cpp_template_argument_type_leaf(node) {
10940        return resolution;
10941    }
10942
10943    // Error recovery can detach a member function from its class while
10944    // leaving an unqualified type argument (for example `error_type` in
10945    // `expected<..., error_type>`). The normal structural scope then lacks
10946    // the class owner and resolves the wrong same-spelled alias, or fails
10947    // closed. The indexed enclosing unit still carries the authoritative
10948    // class scope; retry only this narrowly-shaped leaf with that scope.
10949    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10950        return resolution;
10951    };
10952    let namespace_scope = enclosing_namespace_components(node, source);
10953    if indexed_scope.len() <= namespace_scope.len() {
10954        return resolution;
10955    }
10956    let indexed = resolve_type_components_in_authoritative_scope(
10957        node,
10958        &components,
10959        global,
10960        analyzer,
10961        visibility,
10962        ordinary_type_imports,
10963        file,
10964        source,
10965        None,
10966        false,
10967        false,
10968        false,
10969        indexed_scope,
10970    );
10971    match indexed {
10972        LexicalTypeResolution::Resolved { ref unit, .. }
10973            if !visibility
10974                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10975        {
10976            resolution
10977        }
10978        LexicalTypeResolution::Resolved { .. } => indexed,
10979        _ => resolution,
10980    }
10981}
10982
10983/// Resolve a type lexically, using the target to guide incomplete evidence.
10984/// A lexical resolution visible in context wins over target guidance even when
10985/// its unit and candidates do not include the target.
10986#[allow(clippy::too_many_arguments)]
10987pub fn resolve_type_node_lexically_for_target(
10988    node: Node<'_>,
10989    analyzer: &CppGraphSource<'_>,
10990    visibility: &VisibilityIndex<'_>,
10991    ordinary_type_imports: &OrdinaryTypeImportCell,
10992    file: &ProjectFile,
10993    source: &str,
10994    target: &CodeUnit,
10995    scope_cache: Option<&LexicalScopeCache>,
10996    recovered_scope: Option<&[String]>,
10997) -> LexicalTypeResolution {
10998    let Some((reference_components, global)) = type_reference_components(node, source) else {
10999        return LexicalTypeResolution::Missing;
11000    };
11001    let terminal = reference_components
11002        .last()
11003        .expect("type reference components are non-empty");
11004    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
11005        return LexicalTypeResolution::Missing;
11006    }
11007    let template_arguments = cpp_template_reference_arguments(node, source);
11008    let selects_concrete_specialization =
11009        template_arguments.is_some() && visibility.is_template_specialization(target);
11010    if !selects_concrete_specialization
11011        && !visibility.structured_type_reference_may_resolve_to_target(
11012            analyzer,
11013            file,
11014            std::slice::from_ref(terminal),
11015            false,
11016            &[],
11017            target,
11018        )
11019    {
11020        return LexicalTypeResolution::Missing;
11021    }
11022    if let Some(arguments) = template_arguments.as_ref() {
11023        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
11024            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
11025                node,
11026                &reference_components,
11027                global,
11028                analyzer,
11029                visibility,
11030                ordinary_type_imports,
11031                file,
11032                source,
11033                recovered_scope,
11034            )
11035        } else {
11036            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
11037                node,
11038                &reference_components,
11039                global,
11040                analyzer,
11041                visibility,
11042                ordinary_type_imports,
11043                file,
11044                source,
11045                scope_cache,
11046            )
11047        };
11048        return match alias_resolution {
11049            LexicalTypeResolution::Resolved {
11050                unit,
11051                components,
11052                candidates,
11053            } if visibility.template_alias_arguments_preserve_target(
11054                analyzer, file, &unit, arguments, target,
11055            ) =>
11056            {
11057                LexicalTypeResolution::Resolved {
11058                    unit: target.clone(),
11059                    components,
11060                    candidates,
11061                }
11062            }
11063            LexicalTypeResolution::Resolved {
11064                unit,
11065                components,
11066                candidates,
11067            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
11068                Ok(resolved_unit) => {
11069                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
11070                        .then(|| {
11071                            target_guided_malformed_template_alias_resolution(
11072                                node,
11073                                analyzer,
11074                                visibility,
11075                                file,
11076                                arguments,
11077                                &reference_components,
11078                                target,
11079                            )
11080                        })
11081                        .flatten();
11082                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
11083                        unit: resolved_unit,
11084                        components,
11085                        candidates,
11086                    })
11087                }
11088                Err(_) => LexicalTypeResolution::Ambiguous,
11089            },
11090            LexicalTypeResolution::Missing => {
11091                let target_preserving = if let Some(recovered_scope) = recovered_scope {
11092                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
11093                        node,
11094                        &reference_components,
11095                        global,
11096                        analyzer,
11097                        visibility,
11098                        ordinary_type_imports,
11099                        file,
11100                        source,
11101                        target,
11102                        true,
11103                        recovered_scope,
11104                    )
11105                } else {
11106                    resolve_type_components_lexically_at_for_target_with_scope_cache(
11107                        node,
11108                        &reference_components,
11109                        global,
11110                        analyzer,
11111                        visibility,
11112                        ordinary_type_imports,
11113                        file,
11114                        source,
11115                        target,
11116                        true,
11117                        scope_cache,
11118                    )
11119                };
11120                match target_preserving {
11121                    LexicalTypeResolution::Resolved {
11122                        unit: _,
11123                        components,
11124                        candidates,
11125                    } if template_reference_candidates_select_target(
11126                        node,
11127                        &candidates,
11128                        analyzer,
11129                        visibility,
11130                        file,
11131                        source,
11132                        target,
11133                    ) =>
11134                    {
11135                        LexicalTypeResolution::Resolved {
11136                            unit: target.clone(),
11137                            components,
11138                            candidates,
11139                        }
11140                    }
11141                    _ => target_guided_malformed_template_alias_resolution(
11142                        node,
11143                        analyzer,
11144                        visibility,
11145                        file,
11146                        arguments,
11147                        &reference_components,
11148                        target,
11149                    )
11150                    .unwrap_or(LexicalTypeResolution::Missing),
11151                }
11152            }
11153            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
11154        };
11155    }
11156    match select_qualifier_owner(
11157        node,
11158        global,
11159        analyzer,
11160        visibility,
11161        ordinary_type_imports,
11162        file,
11163        source,
11164    ) {
11165        QualifierOwnerSelection::Unchanged => {}
11166        QualifierOwnerSelection::Selected(components) => {
11167            return resolve_type_components_lexically_at_for_target_with_scope_cache(
11168                node,
11169                &components,
11170                true,
11171                analyzer,
11172                visibility,
11173                ordinary_type_imports,
11174                file,
11175                source,
11176                target,
11177                true,
11178                scope_cache,
11179            );
11180        }
11181        QualifierOwnerSelection::Ambiguous => return LexicalTypeResolution::Ambiguous,
11182    }
11183    let resolution = if let Some(recovered_scope) = recovered_scope {
11184        resolve_type_components_lexically_at_for_target_with_recovered_scope(
11185            node,
11186            &reference_components,
11187            global,
11188            analyzer,
11189            visibility,
11190            ordinary_type_imports,
11191            file,
11192            source,
11193            target,
11194            true,
11195            recovered_scope,
11196        )
11197    } else {
11198        resolve_type_components_lexically_at_for_target_with_scope_cache(
11199            node,
11200            &reference_components,
11201            global,
11202            analyzer,
11203            visibility,
11204            ordinary_type_imports,
11205            file,
11206            source,
11207            target,
11208            true,
11209            scope_cache,
11210        )
11211    };
11212    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
11213        target_guided_qualified_namespace_function_type_resolution(
11214            node,
11215            &reference_components,
11216            global,
11217            analyzer,
11218            visibility,
11219            ordinary_type_imports,
11220            file,
11221            source,
11222            target,
11223        )
11224        .unwrap_or(resolution)
11225    } else {
11226        resolution
11227    };
11228    if !is_cpp_template_argument_type_leaf(node) {
11229        return resolution;
11230    }
11231
11232    // Preprocessor recovery can lift a member declaration out of its class
11233    // field list.  The unqualified template argument is then resolved from
11234    // the namespace only, even though the indexed enclosing callable still
11235    // identifies the class owner.  Retry this exact leaf against that
11236    // structured owner scope; ordinary type nodes must continue to use the
11237    // parser-derived lexical scope so unrelated same-spelled aliases remain
11238    // excluded.
11239    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
11240        return resolution;
11241    };
11242    let namespace_scope = enclosing_namespace_components(node, source);
11243    if indexed_scope.len() <= namespace_scope.len() {
11244        return resolution;
11245    }
11246    let indexed = visibility.resolve_type_components_lexically_for_target(
11247        analyzer,
11248        file,
11249        &reference_components,
11250        global,
11251        &indexed_scope,
11252        target,
11253    );
11254    match indexed {
11255        LexicalTypeResolution::Resolved { ref unit, .. }
11256            if visibility
11257                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11258        {
11259            indexed
11260        }
11261        _ => resolution,
11262    }
11263}
11264
11265/// Recover the lexical namespace of an out-of-line namespace function whose
11266/// relative qualifier is reachable through a `using namespace` directive.
11267///
11268/// Per-file declaration extraction can attach `void schema::consume(...)` to
11269/// the first visible namespace prefix when more than one using-directive is
11270/// active. A matching visible free-function declaration still proves the
11271/// complete namespace. Target-guided inverse lookup may use that namespace to
11272/// retry a parameter or body type, but only when the target lives in the same
11273/// namespace and the declaration's callable arity matches the definition.
11274#[allow(clippy::too_many_arguments)]
11275fn target_guided_qualified_namespace_function_type_resolution(
11276    node: Node<'_>,
11277    components: &[String],
11278    global: bool,
11279    analyzer: &CppGraphSource<'_>,
11280    visibility: &VisibilityIndex<'_>,
11281    ordinary_type_imports: &OrdinaryTypeImportCell,
11282    file: &ProjectFile,
11283    source: &str,
11284    target: &CodeUnit,
11285) -> Option<LexicalTypeResolution> {
11286    let function_definition = std::iter::successors(Some(node), |current| current.parent())
11287        .find(|current| current.kind() == "function_definition")?;
11288    let function = function_definition_name_node(function_definition)?;
11289    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
11290    let target_namespace = target.package_name();
11291    if target_namespace.is_empty() {
11292        return None;
11293    }
11294    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11295        brokk_bifrost_core::analyzer::Language::Cpp,
11296        target_namespace,
11297    );
11298    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
11299    {
11300        return None;
11301    }
11302
11303    let function_name = node_text(function_terminal_node(function), source);
11304    let definition_unit = canonical_callable_at_node(
11305        function_definition,
11306        analyzer,
11307        visibility,
11308        file,
11309        source,
11310        TargetKind::FreeFunction,
11311    )?;
11312    let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
11313    let declaration_proves_namespace = visibility
11314        .visible_identifier_candidates(file, function_name)
11315        .filter(|candidate| {
11316            candidate.is_function()
11317                && type_owner_of(analyzer, candidate).is_none()
11318                && candidate.package_name() == target_namespace
11319        })
11320        .any(|candidate| {
11321            cpp_callable_arity(analyzer, candidate)
11322                .is_none_or(|known| known.accepts(definition_arity))
11323        });
11324    if !declaration_proves_namespace {
11325        return None;
11326    }
11327
11328    let resolution = resolve_type_components_lexically_at_scoped(
11329        node,
11330        components,
11331        global,
11332        analyzer,
11333        visibility,
11334        ordinary_type_imports,
11335        file,
11336        source,
11337        Some(target),
11338        true,
11339        false,
11340        false,
11341        target_scope,
11342    );
11343    match &resolution {
11344        LexicalTypeResolution::Resolved {
11345            unit, candidates, ..
11346        } if (same_visible_symbol(unit, target)
11347            || candidates
11348                .iter()
11349                .any(|candidate| same_visible_symbol(candidate, target)))
11350            && visibility
11351                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11352        {
11353            Some(resolution)
11354        }
11355        LexicalTypeResolution::Resolved { .. }
11356        | LexicalTypeResolution::Ambiguous
11357        | LexicalTypeResolution::Missing => None,
11358    }
11359}
11360
11361#[allow(clippy::too_many_arguments)]
11362fn target_guided_malformed_template_alias_resolution(
11363    node: Node<'_>,
11364    analyzer: &CppGraphSource<'_>,
11365    visibility: &VisibilityIndex<'_>,
11366    file: &ProjectFile,
11367    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
11368    components: &[String],
11369    target: &CodeUnit,
11370) -> Option<LexicalTypeResolution> {
11371    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
11372        return None;
11373    }
11374
11375    let identifier = &components[0];
11376    let namespace =
11377        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
11378    let namespace_name = namespace.join("::");
11379    let candidates = visibility
11380        .visible_identifier_candidates(file, identifier)
11381        .filter(|candidate| {
11382            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
11383                && visibility.type_candidate_may_be_visible_before_reference(
11384                    analyzer,
11385                    file,
11386                    candidate,
11387                    node.start_byte(),
11388                )
11389        })
11390        .cloned()
11391        .collect::<Vec<_>>();
11392    let first = candidates.first()?;
11393    if !candidates
11394        .iter()
11395        .all(|candidate| same_logical_symbol(first, candidate))
11396        || !candidates.iter().all(|candidate| {
11397            visibility.template_alias_arguments_preserve_target(
11398                analyzer, file, candidate, arguments, target,
11399            )
11400        })
11401    {
11402        return None;
11403    }
11404
11405    let mut resolved_components = namespace;
11406    resolved_components.push(identifier.clone());
11407    Some(LexicalTypeResolution::Resolved {
11408        unit: target.clone(),
11409        components: resolved_components,
11410        candidates,
11411    })
11412}
11413
11414fn resolve_type_node_lexically_for_target_without_visibility(
11415    node: Node<'_>,
11416    analyzer: &CppGraphSource<'_>,
11417    visibility: &VisibilityIndex<'_>,
11418    file: &ProjectFile,
11419    source: &str,
11420    target: &CodeUnit,
11421) -> LexicalTypeResolution {
11422    let Some((components, global)) = type_reference_components(node, source) else {
11423        return LexicalTypeResolution::Missing;
11424    };
11425    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
11426        node,
11427        analyzer,
11428        visibility,
11429        file,
11430        source,
11431        true,
11432        recovered_macro_decorated_declarator_type(node)
11433            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11434        &orphaned_namespace_scopes(visibility, file),
11435    ) {
11436        LexicalScopeResolution::Resolved(scope) => scope,
11437        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11438        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11439    };
11440    visibility.resolve_type_components_lexically_for_target(
11441        analyzer,
11442        file,
11443        &components,
11444        global,
11445        &lexical_scope,
11446        target,
11447    )
11448}
11449
11450fn type_node_has_exact_target_identity_without_visibility(
11451    node: Node<'_>,
11452    analyzer: &CppGraphSource<'_>,
11453    visibility: &VisibilityIndex<'_>,
11454    file: &ProjectFile,
11455    source: &str,
11456    target: &CodeUnit,
11457) -> bool {
11458    let Some((components, global)) = type_reference_components(node, source) else {
11459        return false;
11460    };
11461    let LexicalScopeResolution::Resolved(lexical_scope) =
11462        enclosing_lexical_scope_components_with_unresolved_owner(
11463            node,
11464            analyzer,
11465            visibility,
11466            file,
11467            source,
11468            true,
11469            recovered_macro_decorated_declarator_type(node)
11470                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11471            &orphaned_namespace_scopes(visibility, file),
11472        )
11473    else {
11474        return false;
11475    };
11476    let target_name = cpp_name_for(target);
11477    lexical_component_tiers(&components, global, &lexical_scope)
11478        .any(|qualified| qualified.join("::") == target_name)
11479}
11480
11481pub fn resolve_using_enum_declaration_owner(
11482    node: Node<'_>,
11483    analyzer: &CppGraphSource<'_>,
11484    visibility: &VisibilityIndex<'_>,
11485    ordinary_type_imports: &OrdinaryTypeImportCell,
11486    file: &ProjectFile,
11487    source: &str,
11488) -> LexicalTypeResolution {
11489    let Some(type_node) = using_enum_declaration_type_node(node) else {
11490        return LexicalTypeResolution::Missing;
11491    };
11492    let mut components = Vec::new();
11493    if append_cpp_name_components(type_node, source, &mut components).is_none()
11494        || components.is_empty()
11495    {
11496        return LexicalTypeResolution::Missing;
11497    }
11498    resolve_type_components_lexically_at(
11499        type_node,
11500        &components,
11501        is_globally_qualified_cpp_name(type_node),
11502        analyzer,
11503        visibility,
11504        ordinary_type_imports,
11505        file,
11506        source,
11507    )
11508}
11509
11510pub fn resolve_ordinary_using_declaration_owner(
11511    node: Node<'_>,
11512    analyzer: &CppGraphSource<'_>,
11513    visibility: &VisibilityIndex<'_>,
11514    file: &ProjectFile,
11515    source: &str,
11516) -> LexicalTypeResolution {
11517    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
11518        return LexicalTypeResolution::Missing;
11519    };
11520    let mut components = Vec::new();
11521    if append_cpp_name_components(type_node, source, &mut components).is_none()
11522        || components.len() < 2
11523    {
11524        return LexicalTypeResolution::Missing;
11525    }
11526    let lexical_scope =
11527        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
11528            LexicalScopeResolution::Resolved(scope) => scope,
11529            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11530            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11531        };
11532    visibility.resolve_type_components_lexically(
11533        analyzer,
11534        file,
11535        &components,
11536        is_globally_qualified_cpp_name(type_node),
11537        &lexical_scope,
11538    )
11539}
11540
11541pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11542    (node.kind() == "using_declaration"
11543        && (0..node.child_count()).any(|index| {
11544            node.child(index)
11545                .is_some_and(|child| child.kind() == "enum")
11546        }))
11547    .then(|| node.named_child(0))
11548    .flatten()
11549}
11550
11551pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11552    (node.kind() == "using_declaration"
11553        && using_enum_declaration_type_node(node).is_none()
11554        && using_namespace_directive_name_node(node).is_none())
11555    .then(|| node.named_child(0))
11556    .flatten()
11557}
11558
11559/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
11560/// undefined namespace-sentinel macro as a declaration whose type is the
11561/// all-caps sentinel and whose qualified declarator starts with a pseudo
11562/// `using` scope. The real imported name remains a structured qualified
11563/// identifier under that declarator. Recover only this exact CST envelope so
11564/// ordinary macro-decorated variables are not treated as imports.
11565fn recovered_macro_using_declaration_type_node<'tree>(
11566    node: Node<'tree>,
11567    source: &str,
11568) -> Option<(Node<'tree>, bool)> {
11569    if node.kind() != "declaration" {
11570        return None;
11571    }
11572    let macro_type = node.child_by_field_name("type")?;
11573    if macro_type.kind() != "type_identifier"
11574        || !cpp_export_macro_token(node_text(macro_type, source))
11575    {
11576        return None;
11577    }
11578    let declarator = node.child_by_field_name("declarator")?;
11579    if declarator.kind() != "qualified_identifier" {
11580        return None;
11581    }
11582    let scope = declarator.child_by_field_name("scope")?;
11583    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
11584        return None;
11585    }
11586    let target = declarator.child_by_field_name("name")?;
11587    let mut components = Vec::new();
11588    append_cpp_name_components(target, source, &mut components)?;
11589    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
11590}
11591
11592fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11593    let is_directive = node.kind() == "using_directive"
11594        || (node.kind() == "using_declaration"
11595            && (0..node.child_count()).any(|index| {
11596                node.child(index)
11597                    .is_some_and(|child| child.kind() == "namespace")
11598            }));
11599    if !is_directive {
11600        return None;
11601    }
11602    node.child_by_field_name("name")
11603        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11604}
11605
11606fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11607    let mut current = node.parent();
11608    while let Some(parent) = current {
11609        if matches!(
11610            parent.kind(),
11611            "compound_statement"
11612                | "function_definition"
11613                | "lambda_expression"
11614                | "for_statement"
11615                | "while_statement"
11616                | "if_statement"
11617                | "class_specifier"
11618                | "struct_specifier"
11619                | "union_specifier"
11620        ) {
11621            return None;
11622        }
11623        current = parent.parent();
11624    }
11625    Some(enclosing_namespace_components(node, source))
11626}
11627
11628fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11629    let mut current = node.parent();
11630    while let Some(scope) = current {
11631        if matches!(
11632            scope.kind(),
11633            "compound_statement"
11634                | "declaration_list"
11635                | "field_declaration_list"
11636                | "translation_unit"
11637        ) {
11638            let mut depth = 0;
11639            let mut ancestor = scope.parent();
11640            while let Some(parent) = ancestor {
11641                depth += 1;
11642                ancestor = parent.parent();
11643            }
11644            return Some((
11645                scope.start_byte(),
11646                scope.end_byte(),
11647                depth,
11648                scope.kind() == "compound_statement",
11649            ));
11650        }
11651        current = scope.parent();
11652    }
11653    None
11654}
11655
11656/// Build the per-file structured using index for `file`.
11657///
11658/// The result is a pure function of the file's parsed content, which is what
11659/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
11660/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
11661/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
11662pub fn build_source_using_index(
11663    cpp: &dyn CppSource,
11664    token: QueryToken<'_>,
11665    file: &ProjectFile,
11666) -> SourceUsingIndex {
11667    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11668        return SourceUsingIndex::default();
11669    };
11670    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11671}
11672
11673fn collect_source_using_index(
11674    cpp: &dyn CppSource,
11675    source_file: &ProjectFile,
11676    root: Node<'_>,
11677    source: &str,
11678) -> SourceUsingIndex {
11679    #[cfg(not(any(test, feature = "test-support")))]
11680    let _ = cpp;
11681    let mut index = SourceUsingIndex::default();
11682    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11683    let mut stack = vec![root];
11684    while let Some(node) = stack.pop() {
11685        let target = match node.kind() {
11686            "using_directive" | "using_declaration" => {
11687                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11688                    let mut namespace_components = Vec::new();
11689                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
11690                        .map(|_| EffectiveUsingTarget::Namespace {
11691                            namespace_components,
11692                            global: is_globally_qualified_cpp_name(namespace_node),
11693                        })
11694                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11695                    let mut target_components = Vec::new();
11696                    (append_cpp_name_components(type_node, source, &mut target_components)
11697                        .is_some()
11698                        && target_components.len() >= 2)
11699                        .then(|| EffectiveUsingTarget::Ordinary {
11700                            name: target_components
11701                                .last()
11702                                .expect("ordinary using has a terminal component")
11703                                .clone(),
11704                            target_components,
11705                            global: is_globally_qualified_cpp_name(type_node),
11706                        })
11707                } else {
11708                    None
11709                }
11710            }
11711            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11712                |(type_node, global)| {
11713                    let mut target_components = Vec::new();
11714                    (append_cpp_name_components(type_node, source, &mut target_components)
11715                        .is_some()
11716                        && target_components.len() >= 2)
11717                        .then(|| EffectiveUsingTarget::Ordinary {
11718                            name: target_components
11719                                .last()
11720                                .expect("recovered ordinary using has a terminal component")
11721                                .clone(),
11722                            target_components,
11723                            global,
11724                        })
11725                },
11726            ),
11727            _ => None,
11728        };
11729        if let Some(target) = target {
11730            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
11731            // searches from the root. The project index visits every AST node, but only these
11732            // structured using declarations need a guard environment. Keep the cheap target
11733            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
11734            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11735            #[cfg(any(test, feature = "test-support"))]
11736            cpp.record_using_guard_context_inspection_for_test();
11737            let Some(required_guards) = binding_site_guards(node, source) else {
11738                let mut cursor = node.walk();
11739                stack.extend(node.children(&mut cursor));
11740                continue;
11741            };
11742            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11743                ordinary_using_scope(node)
11744            {
11745                let declaration_namespace = enclosing_namespace_components(node, source);
11746                let declaration_namespace = if declaration_namespace.is_empty() {
11747                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11748                        .unwrap_or(declaration_namespace)
11749                } else {
11750                    declaration_namespace
11751                };
11752                let namespace_scope = using_named_scope(node, source);
11753                let lexical_depth = declaration_namespace.len();
11754                let binding = OrdinaryTypeImport {
11755                    target,
11756                    source: source_file.clone(),
11757                    declaration_byte: node.end_byte(),
11758                    scope_start,
11759                    scope_end,
11760                    scope_depth,
11761                    block_scope,
11762                    lexical_depth,
11763                    declaration_namespace,
11764                    namespace_scope,
11765                    resolved_target_components: None,
11766                    required_guards,
11767                };
11768                match &binding.target {
11769                    EffectiveUsingTarget::Ordinary { name, .. } => index
11770                        .ordinary_by_name
11771                        .entry(name.clone())
11772                        .or_default()
11773                        .push(binding),
11774                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11775                }
11776            }
11777        }
11778        let mut cursor = node.walk();
11779        stack.extend(node.children(&mut cursor));
11780    }
11781    index
11782}
11783
11784struct OrphanedNamespaceEnvelope {
11785    body_end: usize,
11786    components: Vec<String>,
11787    class_names: HashSet<String>,
11788}
11789
11790/// Tree-sitter can terminate a namespace body at an object-like namespace
11791/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11792/// out-of-line definitions at translation-unit scope. A block-scoped using
11793/// declaration in one of those definitions still belongs to the namespace
11794/// selected by the malformed namespace envelope. Keep the envelope scan
11795/// source-local and reuse its structural ownership evidence for each using.
11796fn collect_orphaned_namespace_envelopes(
11797    root: Node<'_>,
11798    source: &str,
11799) -> Vec<OrphanedNamespaceEnvelope> {
11800    let mut envelopes = Vec::new();
11801    let mut stack = vec![root];
11802    while let Some(current) = stack.pop() {
11803        if current.kind() == "namespace_definition"
11804            && let Some(body) = current.child_by_field_name("body")
11805            && current.end_byte() == body.end_byte()
11806            && let Some(name) = current.child_by_field_name("name")
11807        {
11808            let mut components = enclosing_namespace_components(current, source);
11809            if append_cpp_name_components(name, source, &mut components).is_some()
11810                && !components.is_empty()
11811            {
11812                let mut class_names = HashSet::default();
11813                let mut body_stack = vec![body];
11814                while let Some(node) = body_stack.pop() {
11815                    if let Some(name) = orphaned_class_definition_name(node, source) {
11816                        class_names.insert(name);
11817                    }
11818                    let mut cursor = node.walk();
11819                    if node.kind() == "ERROR" {
11820                        body_stack.extend(node.children(&mut cursor));
11821                    } else {
11822                        body_stack.extend(node.named_children(&mut cursor));
11823                    }
11824                }
11825                envelopes.push(OrphanedNamespaceEnvelope {
11826                    body_end: body.end_byte(),
11827                    components,
11828                    class_names,
11829                });
11830            }
11831        }
11832        let mut cursor = current.walk();
11833        stack.extend(current.named_children(&mut cursor));
11834    }
11835    envelopes
11836}
11837
11838fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11839    if matches!(
11840        node.kind(),
11841        "class_specifier" | "struct_specifier" | "union_specifier"
11842    ) {
11843        let body = node.child_by_field_name("body")?;
11844        let name = node.child_by_field_name("name")?;
11845        return (!name.is_missing() && !body.is_missing())
11846            .then(|| node_text(name, source).to_string());
11847    }
11848    if node.kind() != "ERROR" {
11849        return None;
11850    }
11851
11852    // When an object-like namespace macro is parsed as a function definition,
11853    // tree-sitter can place the entire class declaration inside an ERROR node
11854    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11855    // fallback structural: accept only a named class-like keyword followed by
11856    // a real body, never an arbitrary identifier mentioned in the envelope.
11857    for index in 0..node.child_count() {
11858        let Some(keyword) = node.child(index) else {
11859            continue;
11860        };
11861        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11862            continue;
11863        }
11864        let mut name = None;
11865        for next_index in (index + 1)..node.child_count() {
11866            let Some(next) = node.child(next_index) else {
11867                continue;
11868            };
11869            if next.kind() == ";" {
11870                break;
11871            }
11872            if next.kind() == "{" {
11873                return name
11874                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11875                    .map(|name_node| node_text(name_node, source).to_string());
11876            }
11877            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11878                name = Some(next);
11879            }
11880        }
11881    }
11882    None
11883}
11884
11885fn recovered_orphaned_namespace_components(
11886    node: Node<'_>,
11887    source: &str,
11888    envelopes: &[OrphanedNamespaceEnvelope],
11889) -> Option<Vec<String>> {
11890    let owner_name = orphaned_using_owner_name(node, source)?;
11891    envelopes
11892        .iter()
11893        .filter(|envelope| {
11894            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11895        })
11896        .max_by_key(|envelope| envelope.body_end)
11897        .map(|envelope| envelope.components.clone())
11898}
11899
11900fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11901    let function = std::iter::successors(node.parent(), |current| current.parent())
11902        .find(|current| current.kind() == "function_definition")?;
11903    let owner = function_definition_owner_lookup_node(function)?;
11904    let scope = owner.child_by_field_name("scope")?;
11905    let mut components = Vec::new();
11906    append_cpp_name_components(scope, source, &mut components)?;
11907    // A qualified out-of-line member definition already carries its namespace
11908    // in the declarator scope (for example `foo::Widget::run`). The recovery
11909    // path is only for parser-orphaned top-level members whose owner scope
11910    // collapsed to the bare class name; requiring that shape prevents an
11911    // earlier, unrelated namespace/class from leaking into a global function's
11912    // using-directive lookup.
11913    if components.len() != 1 {
11914        return None;
11915    }
11916    components.pop()
11917}
11918
11919fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11920    let started = Instant::now();
11921    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11922    let source_files = visibility.all_visible_source_files();
11923    if report_stats {
11924        eprintln!(
11925            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11926            source_files.len()
11927        );
11928    }
11929    let mut project = ProjectUsingIndex::default();
11930    let mut ordinary_bindings = 0usize;
11931    for source_file in &source_files {
11932        // The per-file index is memoized on the analyzer, so assembling the
11933        // project index for a fresh `VisibilityIndex` copies bindings instead
11934        // of re-walking each file's AST (#1927).
11935        let source_index = visibility
11936            .cpp()
11937            .source_using_index(visibility.token(), source_file);
11938        for (name, bindings) in &source_index.ordinary_by_name {
11939            ordinary_bindings += bindings.len();
11940            project
11941                .ordinary_by_name
11942                .entry(name.clone())
11943                .or_default()
11944                .extend(bindings.iter().cloned());
11945        }
11946        project
11947            .directives
11948            .extend(source_index.directives.iter().cloned());
11949    }
11950    if report_stats {
11951        eprintln!(
11952            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11953            source_files.len(),
11954            project.ordinary_by_name.len(),
11955            ordinary_bindings,
11956            project.directives.len(),
11957            started.elapsed().as_millis(),
11958        );
11959    }
11960    project
11961}
11962
11963fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11964    visibility.project_using_index(|| build_project_using_index(visibility))
11965}
11966
11967/// Build the immutable project-wide using index before a parallel file scan.
11968///
11969/// Keeping the `OnceLock` publication here avoids making every scanner carry
11970/// an eager index, while callers that are about to fan out can prevent one
11971/// worker from doing the whole build as its peers wait on the lock.
11972pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11973    let _ = project_using_index(visibility);
11974}
11975
11976fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11977    let (components, global) = match &binding.target {
11978        EffectiveUsingTarget::Ordinary {
11979            target_components,
11980            global,
11981            ..
11982        } => (target_components, *global),
11983        EffectiveUsingTarget::Namespace {
11984            namespace_components,
11985            global,
11986        } => (namespace_components, *global),
11987    };
11988    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11989}
11990
11991fn using_binding_target_components_for_name(
11992    binding: &OrdinaryTypeImport,
11993    project: &ProjectUsingIndex,
11994    visibility: &VisibilityIndex<'_>,
11995    file: &ProjectFile,
11996    name: &str,
11997) -> Option<Vec<String>> {
11998    // Built once per call rather than per candidate: the filter runs over every
11999    // visible identifier of `name`, and the source is the same object each time.
12000    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
12001    let visible_candidates = visibility
12002        .visible_identifier_candidates(file, name)
12003        .filter(|candidate| {
12004            candidate.is_class()
12005                || declared_type_alias(&cpp_source, candidate)
12006                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
12007        })
12008        .collect::<Vec<_>>();
12009    if visible_candidates.is_empty() {
12010        return None;
12011    }
12012    match &binding.target {
12013        EffectiveUsingTarget::Ordinary {
12014            name: imported_name,
12015            ..
12016        } if imported_name == name => {
12017            effective_using_target_tiers(binding)
12018                .into_iter()
12019                .find(|qualified| {
12020                    let qualified_name = qualified.join("::");
12021                    visible_candidates
12022                        .iter()
12023                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
12024                })
12025        }
12026        EffectiveUsingTarget::Namespace { .. } => {
12027            visibility.note_using_namespace_lookup_for_test();
12028            let target_tiers = effective_using_target_tiers(binding);
12029            let resolved = target_tiers
12030                .iter()
12031                .find(|namespace_components| {
12032                    let namespace = namespace_components.join("::");
12033                    visible_candidates.iter().any(|candidate| {
12034                        visibility.note_using_name_candidate_inspection_for_test();
12035                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
12036                            candidate_namespace == namespace
12037                                || candidate_namespace.starts_with(&format!("{namespace}::"))
12038                        })
12039                    }) || project.directives.iter().any(|candidate| {
12040                        candidate.namespace_scope.as_deref()
12041                            == Some(namespace_components.as_slice())
12042                    }) || project
12043                        .ordinary_by_name
12044                        .values()
12045                        .flatten()
12046                        .any(|candidate| {
12047                            candidate.namespace_scope.as_deref()
12048                                == Some(namespace_components.as_slice())
12049                        })
12050                })
12051                .cloned();
12052            resolved.or_else(|| {
12053                // A sole lexical namespace tier is itself enough to retain the
12054                // directive. Candidate identity is resolved later, where
12055                // target guidance and macro-expanded owner names are available.
12056                // Dropping it here makes an unrelated same-terminal type hide
12057                // the actual namespace member before lookup can compare owners.
12058                (target_tiers.len() == 1)
12059                    .then(|| target_tiers.into_iter().next())
12060                    .flatten()
12061            })
12062        }
12063        EffectiveUsingTarget::Ordinary { .. } => None,
12064    }
12065}
12066
12067fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
12068    let start = activation.checked_sub(1)?;
12069    let mut node = root.descendant_for_byte_range(start, activation)?;
12070    while node.kind() != "preproc_include" {
12071        node = node.parent()?;
12072    }
12073    Some(node)
12074}
12075
12076fn project_using_bindings(
12077    binding: OrdinaryTypeImport,
12078    visibility: &VisibilityIndex<'_>,
12079    file: &ProjectFile,
12080    root: Node<'_>,
12081    source: &str,
12082) -> Vec<OrdinaryTypeImport> {
12083    if binding.source == *file {
12084        return vec![binding];
12085    }
12086    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
12087        return Vec::new();
12088    }
12089    visibility.note_using_donor_activation_for_test();
12090    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
12091        return Vec::new();
12092    };
12093    let projections = visibility
12094        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
12095        .map_or_else(
12096            || {
12097                visibility.conditional_include_projections_for_source(
12098                    file,
12099                    prepared.as_ref(),
12100                    &binding.source,
12101                )
12102            },
12103            |activation_byte| {
12104                Arc::from([ConditionalIncludeProjection {
12105                    activation_byte,
12106                    required_guards: HashSet::default(),
12107                    partial_guards: HashSet::default(),
12108                }])
12109            },
12110        );
12111    projections
12112        .iter()
12113        .cloned()
12114        .filter_map(|projection| {
12115            let required_guards =
12116                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
12117            let mut projected = binding.clone();
12118            projected.required_guards = required_guards;
12119            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
12120        })
12121        .collect()
12122}
12123
12124fn project_using_binding_at_activation(
12125    mut binding: OrdinaryTypeImport,
12126    activation: usize,
12127    root: Node<'_>,
12128    source: &str,
12129) -> Option<OrdinaryTypeImport> {
12130    let include = include_node_for_activation(root, activation)?;
12131    let include_namespace = enclosing_namespace_components(include, source);
12132    let mut declaration_namespace = include_namespace.clone();
12133    declaration_namespace.extend(binding.declaration_namespace);
12134    binding.declaration_namespace = declaration_namespace;
12135    binding.declaration_byte = activation;
12136    if let Some(prefix) = using_named_scope(include, source) {
12137        let mut projected = prefix;
12138        projected.extend(binding.namespace_scope.take().unwrap_or_default());
12139        binding.scope_depth = projected.len();
12140        binding.block_scope = false;
12141        binding.lexical_depth = projected.len();
12142        binding.namespace_scope = Some(projected);
12143        binding.scope_start = 0;
12144        binding.scope_end = usize::MAX;
12145        Some(binding)
12146    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
12147        binding.namespace_scope = None;
12148        binding.scope_start = start;
12149        binding.scope_end = end;
12150        binding.scope_depth = depth;
12151        binding.block_scope = block_scope;
12152        binding.lexical_depth = include_namespace.len();
12153        Some(binding)
12154    } else {
12155        None
12156    }
12157}
12158
12159/// `node` may be any node of `file`'s tree; the projection reaches the tree
12160/// root itself when it needs one. `Node::parent` re-descends from the root on
12161/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
12162/// call site cost a near-full-AST scan per reference on a large flat file
12163/// (#1927); a name with no candidate bindings never pays for it.
12164pub fn effective_using_bindings_for_name(
12165    visibility: &VisibilityIndex<'_>,
12166    imports: &OrdinaryTypeImportCell,
12167    file: &ProjectFile,
12168    node: Node<'_>,
12169    source: &str,
12170    name: &str,
12171) -> Arc<[OrdinaryTypeImport]> {
12172    imports
12173        .projection_cell(name)
12174        .get_or_init(|| {
12175            let project = project_using_index(visibility);
12176            let name_bindings = project.ordinary_by_name.get(name);
12177            if name_bindings.is_none() && project.directives.is_empty() {
12178                return Arc::from(Vec::new());
12179            }
12180            let root = root_node(node);
12181            let mut projected = Vec::new();
12182            for binding in name_bindings
12183                .into_iter()
12184                .flatten()
12185                .chain(project.directives.iter())
12186            {
12187                if !visibility.source_is_visible(file, &binding.source) {
12188                    continue;
12189                }
12190                let target_components = using_binding_target_components_for_name(
12191                    binding, project, visibility, file, name,
12192                )
12193                .or_else(|| match &binding.target {
12194                    EffectiveUsingTarget::Ordinary {
12195                        name: imported_name,
12196                        target_components,
12197                        ..
12198                    } if imported_name == name => Some(target_components.clone()),
12199                    EffectiveUsingTarget::Ordinary { .. }
12200                    | EffectiveUsingTarget::Namespace { .. } => None,
12201                });
12202                let Some(target_components) = target_components else {
12203                    continue;
12204                };
12205                let mut binding = binding.clone();
12206                binding.resolved_target_components = Some(target_components);
12207                projected.extend(project_using_bindings(
12208                    binding, visibility, file, root, source,
12209                ));
12210            }
12211            Arc::from(projected)
12212        })
12213        .clone()
12214}
12215
12216pub fn initialized_ordinary_type_imports(
12217    root: Node<'_>,
12218    analyzer: &CppGraphSource<'_>,
12219    visibility: &VisibilityIndex<'_>,
12220    file: &ProjectFile,
12221    source: &str,
12222) -> OrdinaryTypeImportCell {
12223    let cell = visibility.ordinary_type_import_cell(file);
12224    let _ = (root, analyzer, source);
12225    cell
12226}
12227
12228/// `reference_guards` is the reference node's guard environment, computed once
12229/// by the caller and shared across every binding: recomputing it per binding
12230/// repeated a full ancestor climb whose every `Node::parent` step re-descends
12231/// from the root (#1927).
12232fn effective_using_binding_active(
12233    binding: &OrdinaryTypeImport,
12234    node: Node<'_>,
12235    lexical_scope: &[String],
12236    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12237    visibility: &VisibilityIndex<'_>,
12238    file: &ProjectFile,
12239) -> bool {
12240    effective_using_binding_guards_active(
12241        binding,
12242        node.start_byte(),
12243        reference_guards,
12244        visibility,
12245        file,
12246    ) && binding.namespace_scope.as_ref().map_or_else(
12247        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12248        |namespace| lexical_scope.starts_with(namespace),
12249    )
12250}
12251
12252fn effective_using_binding_guards_active(
12253    binding: &OrdinaryTypeImport,
12254    reference_byte: usize,
12255    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12256    visibility: &VisibilityIndex<'_>,
12257    file: &ProjectFile,
12258) -> bool {
12259    binding.declaration_byte <= reference_byte
12260        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
12261        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12262}
12263
12264fn effective_using_binding_guards_compatible(
12265    binding: &OrdinaryTypeImport,
12266    reference_byte: usize,
12267    reference_guards: Option<&HashSet<PreprocessorGuard>>,
12268    visibility: &VisibilityIndex<'_>,
12269    file: &ProjectFile,
12270) -> bool {
12271    binding.source != *file
12272        && !binding.required_guards.is_empty()
12273        && binding.declaration_byte <= reference_byte
12274        && reference_guards.is_some_and(|active| {
12275            !binding.required_guards.is_subset(active)
12276                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
12277        })
12278        && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
12279}
12280
12281fn effective_using_binding_guards_stable_between(
12282    binding: &OrdinaryTypeImport,
12283    reference_byte: usize,
12284    visibility: &VisibilityIndex<'_>,
12285    file: &ProjectFile,
12286) -> bool {
12287    visibility.preprocessor_guards_stable_between(
12288        file,
12289        binding.declaration_byte,
12290        reference_byte,
12291        &binding.required_guards,
12292    )
12293}
12294
12295#[allow(clippy::too_many_arguments)]
12296fn binding_type_candidates(
12297    binding: &OrdinaryTypeImport,
12298    active_bindings: &[&OrdinaryTypeImport],
12299    analyzer: &CppGraphSource<'_>,
12300    visibility: &VisibilityIndex<'_>,
12301    file: &ProjectFile,
12302    name: &str,
12303    direct_target: Option<&CodeUnit>,
12304    reference_byte: usize,
12305) -> Vec<(CodeUnit, Vec<String>)> {
12306    ordinary_binding_lookup_targets(binding, active_bindings, name)
12307        .into_iter()
12308        .flat_map(|target| {
12309            let mut candidates = visibility
12310                .visible_identifier_candidates(file, name)
12311                .filter(|candidate| {
12312                    (candidate.is_class() || declared_type_alias(analyzer, candidate))
12313                        && type_candidate_matches_lookup_components(
12314                            analyzer,
12315                            visibility,
12316                            file,
12317                            candidate,
12318                            reference_byte,
12319                            &target,
12320                        )
12321                })
12322                .cloned()
12323                .collect::<Vec<_>>();
12324            if candidates.is_empty()
12325                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12326                && let Some(target_unit) = direct_target
12327            {
12328                let expanded_target_name = macro_expanded_cpp_name_components(
12329                    visibility,
12330                    file,
12331                    target_unit,
12332                    reference_byte,
12333                );
12334                if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
12335                    && expanded_target_name == target
12336                    && visibility.external_type_candidate_visible_at(
12337                        file,
12338                        target_unit,
12339                        reference_byte,
12340                    )
12341                {
12342                    candidates.push(target_unit.clone());
12343                }
12344            }
12345            if candidates.is_empty()
12346                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
12347                && let Some(target_unit) = direct_target
12348            {
12349                let visible_types = visibility
12350                    .visible_identifier_candidates(file, name)
12351                    .filter(|candidate| {
12352                        candidate.is_class() || declared_type_alias(analyzer, candidate)
12353                    })
12354                    .collect::<Vec<_>>();
12355                let uniquely_names_target = !visible_types.is_empty()
12356                    && visible_types
12357                        .iter()
12358                        .all(|candidate| same_visible_symbol(candidate, target_unit));
12359                if uniquely_names_target {
12360                    candidates.extend(visible_types.into_iter().cloned());
12361                }
12362            }
12363            candidates
12364                .into_iter()
12365                .map(move |candidate| (candidate, target.clone()))
12366        })
12367        .collect()
12368}
12369
12370fn macro_expanded_cpp_name_components(
12371    visibility: &VisibilityIndex<'_>,
12372    file: &ProjectFile,
12373    unit: &CodeUnit,
12374    reference_byte: usize,
12375) -> Vec<String> {
12376    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12377        brokk_bifrost_core::analyzer::Language::Cpp,
12378        &cpp_name_for(unit),
12379    )
12380    .into_iter()
12381    .flat_map(|component| {
12382        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
12383    })
12384    .collect()
12385}
12386
12387fn macro_expanded_cpp_name_component(
12388    visibility: &VisibilityIndex<'_>,
12389    file: &ProjectFile,
12390    component: String,
12391    reference_byte: usize,
12392) -> Vec<String> {
12393    let Some(replacement) =
12394        visibility.object_macro_replacement_at(file, &component, reference_byte)
12395    else {
12396        return vec![component];
12397    };
12398    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12399        brokk_bifrost_core::analyzer::Language::Cpp,
12400        &replacement,
12401    );
12402    if expanded.is_empty() {
12403        vec![component]
12404    } else {
12405        expanded
12406    }
12407}
12408
12409/// Whether a visible type has the requested qualified spelling.
12410///
12411/// Members of an inline namespace are also members of its enclosing namespace,
12412/// so an ordinary using-declaration may legally omit the inline component. The
12413/// stored FQ name retains that component to keep declarations distinct. Recover
12414/// the omitted spellings from the candidate declaration's namespace ancestors,
12415/// using CST `inline` tokens rather than guessing from names.
12416fn type_candidate_matches_lookup_components(
12417    analyzer: &CppGraphSource<'_>,
12418    visibility: &VisibilityIndex<'_>,
12419    file: &ProjectFile,
12420    candidate: &CodeUnit,
12421    reference_byte: usize,
12422    target: &[String],
12423) -> bool {
12424    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
12425    if expanded == target {
12426        return true;
12427    }
12428    let Some(cpp) = analyzer.cpp else {
12429        return false;
12430    };
12431    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
12432        return false;
12433    };
12434    let root = prepared.tree().root_node();
12435    for range in analyzer.ranges(candidate) {
12436        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
12437        else {
12438            continue;
12439        };
12440        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
12441        loop {
12442            if current.kind() == "namespace_definition"
12443                && let Some(name) = current.child_by_field_name("name")
12444            {
12445                let mut components = Vec::new();
12446                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
12447                    && !components.is_empty()
12448                {
12449                    let inline = (0..current.child_count())
12450                        .filter_map(|index| current.child(index))
12451                        .any(|child| !child.is_named() && child.kind() == "inline");
12452                    namespaces.push((components, inline));
12453                }
12454            }
12455            let Some(parent) = current.parent() else {
12456                break;
12457            };
12458            current = parent;
12459        }
12460        namespaces.reverse();
12461        let mut namespace_components = Vec::new();
12462        let mut inline_indexes = HashSet::default();
12463        for (components, inline) in namespaces {
12464            for component in components {
12465                let expanded_component =
12466                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
12467                if inline {
12468                    inline_indexes.extend(
12469                        namespace_components.len()
12470                            ..namespace_components.len() + expanded_component.len(),
12471                    );
12472                }
12473                namespace_components.extend(expanded_component);
12474            }
12475        }
12476        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
12477            continue;
12478        }
12479        // Each inline namespace component can be present or elided. Compare
12480        // those alternatives as a small dynamic program instead of generating
12481        // every subset of a deeply nested inline-namespace chain.
12482        let mut reachable = vec![false; target.len() + 1];
12483        reachable[0] = true;
12484        for (index, component) in expanded.iter().enumerate() {
12485            let mut next = vec![false; target.len() + 1];
12486            for (target_index, reached) in reachable.iter().copied().enumerate() {
12487                if !reached {
12488                    continue;
12489                }
12490                if inline_indexes.contains(&index) {
12491                    next[target_index] = true;
12492                }
12493                if target
12494                    .get(target_index)
12495                    .is_some_and(|target_component| target_component == component)
12496                {
12497                    next[target_index + 1] = true;
12498                }
12499            }
12500            reachable = next;
12501        }
12502        if reachable[target.len()] {
12503            return true;
12504        }
12505    }
12506    false
12507}
12508
12509#[allow(clippy::too_many_arguments)]
12510fn resolved_type_import(
12511    candidates: Vec<(CodeUnit, Vec<String>)>,
12512    lexical_depth: usize,
12513    is_direct: bool,
12514    analyzer: &CppGraphSource<'_>,
12515    visibility: &VisibilityIndex<'_>,
12516    file: &ProjectFile,
12517    direct_target: Option<&CodeUnit>,
12518) -> OrdinaryTypeImportResolution {
12519    let logical = deduplicate_type_import_candidates(candidates);
12520    let selected = match logical.as_slice() {
12521        [] => return OrdinaryTypeImportResolution::Missing,
12522        [only] => only,
12523        // Several declarations of one FQN in one file are configuration
12524        // spellings of one entity, not competing types (#1845): the imported
12525        // name is unambiguous, only the branch that supplies it depends on the
12526        // build.
12527        several => {
12528            let units = several
12529                .iter()
12530                .map(|(unit, _)| unit)
12531                .collect::<Vec<&CodeUnit>>();
12532            let Some(spelling) = direct_target.and_then(|target| {
12533                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
12534            }) else {
12535                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
12536            };
12537            several
12538                .iter()
12539                .find(|(unit, _)| same_symbol(unit, spelling))
12540                .expect("the selected spelling is one of the imported candidates")
12541        }
12542    };
12543    OrdinaryTypeImportResolution::Resolved {
12544        target: selected.0.clone(),
12545        target_components: selected.1.clone(),
12546        lexical_depth,
12547        is_direct,
12548    }
12549}
12550
12551#[allow(clippy::too_many_arguments)]
12552fn ordinary_type_import_resolution(
12553    node: Node<'_>,
12554    components: &[String],
12555    global: bool,
12556    analyzer: &CppGraphSource<'_>,
12557    visibility: &VisibilityIndex<'_>,
12558    imports: &OrdinaryTypeImportCell,
12559    file: &ProjectFile,
12560    source: &str,
12561    lexical_scope: &[String],
12562    direct_target: Option<&CodeUnit>,
12563) -> OrdinaryTypeImportResolution {
12564    if global || components.len() != 1 {
12565        return OrdinaryTypeImportResolution::Missing;
12566    }
12567    let name = &components[0];
12568    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12569    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
12570    // step re-descends from the root (#1927). A name with no bindings needs
12571    // none of it, and one environment serves every binding of the reference.
12572    if bindings.is_empty() {
12573        return OrdinaryTypeImportResolution::Missing;
12574    }
12575    let reference_guards = preprocessor_guard_environment(node, source);
12576    let active = bindings
12577        .iter()
12578        .filter(|binding| {
12579            effective_using_binding_active(
12580                binding,
12581                node,
12582                lexical_scope,
12583                reference_guards.as_ref(),
12584                visibility,
12585                file,
12586            )
12587        })
12588        .collect::<Vec<_>>();
12589    let transitive = bindings
12590        .iter()
12591        .filter(|binding| {
12592            effective_using_binding_guards_active(
12593                binding,
12594                node.start_byte(),
12595                reference_guards.as_ref(),
12596                visibility,
12597                file,
12598            ) && (binding.namespace_scope.is_some()
12599                || (binding.scope_start <= node.start_byte()
12600                    && node.end_byte() <= binding.scope_end))
12601        })
12602        .collect::<Vec<_>>();
12603    ordinary_type_import_resolution_for_bindings(
12604        node,
12605        name,
12606        analyzer,
12607        visibility,
12608        file,
12609        lexical_scope,
12610        direct_target,
12611        &active,
12612        &transitive,
12613    )
12614}
12615
12616#[allow(clippy::too_many_arguments)]
12617fn compatible_foreign_type_import_resolution(
12618    node: Node<'_>,
12619    components: &[String],
12620    global: bool,
12621    analyzer: &CppGraphSource<'_>,
12622    visibility: &VisibilityIndex<'_>,
12623    imports: &OrdinaryTypeImportCell,
12624    file: &ProjectFile,
12625    source: &str,
12626    lexical_scope: &[String],
12627    direct_target: Option<&CodeUnit>,
12628) -> OrdinaryTypeImportResolution {
12629    if global || components.len() != 1 {
12630        return OrdinaryTypeImportResolution::Missing;
12631    }
12632    let name = &components[0];
12633    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12634    if bindings.is_empty() {
12635        return OrdinaryTypeImportResolution::Missing;
12636    }
12637    let reference_guards = preprocessor_guard_environment(node, source);
12638    let compatible = bindings
12639        .iter()
12640        .filter(|binding| {
12641            effective_using_binding_guards_compatible(
12642                binding,
12643                node.start_byte(),
12644                reference_guards.as_ref(),
12645                visibility,
12646                file,
12647            ) && binding.namespace_scope.as_ref().map_or_else(
12648                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12649                |namespace| lexical_scope.starts_with(namespace),
12650            )
12651        })
12652        .collect::<Vec<_>>();
12653    let transitive = bindings
12654        .iter()
12655        .filter(|binding| {
12656            effective_using_binding_guards_compatible(
12657                binding,
12658                node.start_byte(),
12659                reference_guards.as_ref(),
12660                visibility,
12661                file,
12662            ) && (binding.namespace_scope.is_some()
12663                || (binding.scope_start <= node.start_byte()
12664                    && node.end_byte() <= binding.scope_end))
12665        })
12666        .collect::<Vec<_>>();
12667    ordinary_type_import_resolution_for_bindings(
12668        node,
12669        name,
12670        analyzer,
12671        visibility,
12672        file,
12673        lexical_scope,
12674        direct_target,
12675        &compatible,
12676        &transitive,
12677    )
12678}
12679
12680#[allow(clippy::too_many_arguments)]
12681fn ordinary_type_import_resolution_for_bindings(
12682    node: Node<'_>,
12683    name: &str,
12684    analyzer: &CppGraphSource<'_>,
12685    visibility: &VisibilityIndex<'_>,
12686    file: &ProjectFile,
12687    lexical_scope: &[String],
12688    direct_target: Option<&CodeUnit>,
12689    active: &[&OrdinaryTypeImport],
12690    transitive: &[&OrdinaryTypeImport],
12691) -> OrdinaryTypeImportResolution {
12692    let mut concrete_depths = active
12693        .iter()
12694        .filter(|binding| binding.namespace_scope.is_none())
12695        .map(|binding| binding.scope_depth)
12696        .collect::<Vec<_>>();
12697    concrete_depths.sort_unstable();
12698    concrete_depths.dedup();
12699    for depth in concrete_depths.into_iter().rev() {
12700        let at_tier = active
12701            .iter()
12702            .copied()
12703            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12704        let direct = at_tier
12705            .clone()
12706            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12707            .flat_map(|binding| {
12708                binding_type_candidates(
12709                    binding,
12710                    transitive,
12711                    analyzer,
12712                    visibility,
12713                    file,
12714                    name,
12715                    direct_target,
12716                    node.start_byte(),
12717                )
12718            })
12719            .collect::<Vec<_>>();
12720        if !direct.is_empty() {
12721            return resolved_type_import(
12722                direct,
12723                lexical_scope.len(),
12724                true,
12725                analyzer,
12726                visibility,
12727                file,
12728                direct_target,
12729            );
12730        }
12731        let directives = at_tier
12732            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12733            .flat_map(|binding| {
12734                binding_type_candidates(
12735                    binding,
12736                    transitive,
12737                    analyzer,
12738                    visibility,
12739                    file,
12740                    name,
12741                    direct_target,
12742                    node.start_byte(),
12743                )
12744            })
12745            .collect::<Vec<_>>();
12746        if !directives.is_empty() {
12747            return resolved_type_import(
12748                directives,
12749                lexical_scope.len(),
12750                false,
12751                analyzer,
12752                visibility,
12753                file,
12754                direct_target,
12755            );
12756        }
12757    }
12758    for prefix_len in (0..=lexical_scope.len()).rev() {
12759        let tier = &lexical_scope[..prefix_len];
12760        let at_tier = active
12761            .iter()
12762            .copied()
12763            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12764        let direct = at_tier
12765            .clone()
12766            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12767            .flat_map(|binding| {
12768                binding_type_candidates(
12769                    binding,
12770                    transitive,
12771                    analyzer,
12772                    visibility,
12773                    file,
12774                    name,
12775                    direct_target,
12776                    node.start_byte(),
12777                )
12778            })
12779            .collect::<Vec<_>>();
12780        if !direct.is_empty() {
12781            return resolved_type_import(
12782                direct,
12783                prefix_len,
12784                true,
12785                analyzer,
12786                visibility,
12787                file,
12788                direct_target,
12789            );
12790        }
12791        let directives = at_tier
12792            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12793            .flat_map(|binding| {
12794                binding_type_candidates(
12795                    binding,
12796                    transitive,
12797                    analyzer,
12798                    visibility,
12799                    file,
12800                    name,
12801                    direct_target,
12802                    node.start_byte(),
12803                )
12804            })
12805            .collect::<Vec<_>>();
12806        if !directives.is_empty() {
12807            return resolved_type_import(
12808                directives,
12809                prefix_len,
12810                false,
12811                analyzer,
12812                visibility,
12813                file,
12814                direct_target,
12815            );
12816        }
12817    }
12818    OrdinaryTypeImportResolution::Missing
12819}
12820
12821#[allow(clippy::too_many_arguments)]
12822pub fn resolve_type_components_lexically_at(
12823    node: Node<'_>,
12824    components: &[String],
12825    global: bool,
12826    analyzer: &CppGraphSource<'_>,
12827    visibility: &VisibilityIndex<'_>,
12828    ordinary_type_imports: &OrdinaryTypeImportCell,
12829    file: &ProjectFile,
12830    source: &str,
12831) -> LexicalTypeResolution {
12832    resolve_type_components_lexically_at_inner(
12833        node,
12834        components,
12835        global,
12836        analyzer,
12837        visibility,
12838        ordinary_type_imports,
12839        file,
12840        source,
12841        None,
12842        false,
12843        false,
12844        false,
12845        None,
12846    )
12847}
12848
12849/// Resolve a type at its lexical reference site while retaining the identity
12850/// of an alias that C++ lookup selects.
12851///
12852/// Forward navigation uses the selected spelling as its destination, whereas
12853/// graph attribution normally canonicalizes an alias to its target. Both
12854/// surfaces must still apply the same ordinary using-declarations, declaration
12855/// order, guard state, and lexical-depth precedence.
12856#[allow(clippy::too_many_arguments)]
12857pub fn resolve_type_components_lexically_at_preserving_alias(
12858    node: Node<'_>,
12859    components: &[String],
12860    global: bool,
12861    analyzer: &CppGraphSource<'_>,
12862    visibility: &VisibilityIndex<'_>,
12863    file: &ProjectFile,
12864    source: &str,
12865) -> LexicalTypeResolution {
12866    let ordinary_type_imports =
12867        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12868    resolve_type_components_lexically_at_inner(
12869        node,
12870        components,
12871        global,
12872        analyzer,
12873        visibility,
12874        &ordinary_type_imports,
12875        file,
12876        source,
12877        None,
12878        false,
12879        true,
12880        true,
12881        None,
12882    )
12883}
12884
12885#[allow(clippy::too_many_arguments)]
12886fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12887    node: Node<'_>,
12888    components: &[String],
12889    global: bool,
12890    analyzer: &CppGraphSource<'_>,
12891    visibility: &VisibilityIndex<'_>,
12892    ordinary_type_imports: &OrdinaryTypeImportCell,
12893    file: &ProjectFile,
12894    source: &str,
12895    scope_cache: Option<&LexicalScopeCache>,
12896) -> LexicalTypeResolution {
12897    resolve_type_components_lexically_at_inner(
12898        node,
12899        components,
12900        global,
12901        analyzer,
12902        visibility,
12903        ordinary_type_imports,
12904        file,
12905        source,
12906        None,
12907        false,
12908        true,
12909        false,
12910        scope_cache,
12911    )
12912}
12913
12914#[allow(clippy::too_many_arguments)]
12915fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12916    node: Node<'_>,
12917    components: &[String],
12918    global: bool,
12919    analyzer: &CppGraphSource<'_>,
12920    visibility: &VisibilityIndex<'_>,
12921    ordinary_type_imports: &OrdinaryTypeImportCell,
12922    file: &ProjectFile,
12923    source: &str,
12924    target: &CodeUnit,
12925    apply_structured_prefilter: bool,
12926    scope_cache: Option<&LexicalScopeCache>,
12927) -> LexicalTypeResolution {
12928    resolve_type_components_lexically_at_inner(
12929        node,
12930        components,
12931        global,
12932        analyzer,
12933        visibility,
12934        ordinary_type_imports,
12935        file,
12936        source,
12937        Some(target),
12938        apply_structured_prefilter,
12939        false,
12940        false,
12941        scope_cache,
12942    )
12943}
12944
12945#[allow(clippy::too_many_arguments)]
12946fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12947    node: Node<'_>,
12948    components: &[String],
12949    global: bool,
12950    analyzer: &CppGraphSource<'_>,
12951    visibility: &VisibilityIndex<'_>,
12952    ordinary_type_imports: &OrdinaryTypeImportCell,
12953    file: &ProjectFile,
12954    source: &str,
12955    recovered_scope: &[String],
12956) -> LexicalTypeResolution {
12957    resolve_type_components_in_authoritative_scope(
12958        node,
12959        components,
12960        global,
12961        analyzer,
12962        visibility,
12963        ordinary_type_imports,
12964        file,
12965        source,
12966        None,
12967        false,
12968        true,
12969        false,
12970        recovered_scope.to_vec(),
12971    )
12972}
12973
12974#[allow(clippy::too_many_arguments)]
12975fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12976    node: Node<'_>,
12977    components: &[String],
12978    global: bool,
12979    analyzer: &CppGraphSource<'_>,
12980    visibility: &VisibilityIndex<'_>,
12981    ordinary_type_imports: &OrdinaryTypeImportCell,
12982    file: &ProjectFile,
12983    source: &str,
12984    target: &CodeUnit,
12985    apply_structured_prefilter: bool,
12986    recovered_scope: &[String],
12987) -> LexicalTypeResolution {
12988    resolve_type_components_in_authoritative_scope(
12989        node,
12990        components,
12991        global,
12992        analyzer,
12993        visibility,
12994        ordinary_type_imports,
12995        file,
12996        source,
12997        Some(target),
12998        apply_structured_prefilter,
12999        false,
13000        false,
13001        recovered_scope.to_vec(),
13002    )
13003}
13004
13005#[allow(clippy::too_many_arguments)]
13006fn resolve_type_components_lexically_at_inner(
13007    node: Node<'_>,
13008    components: &[String],
13009    global: bool,
13010    analyzer: &CppGraphSource<'_>,
13011    visibility: &VisibilityIndex<'_>,
13012    ordinary_type_imports: &OrdinaryTypeImportCell,
13013    file: &ProjectFile,
13014    source: &str,
13015    direct_target: Option<&CodeUnit>,
13016    apply_structured_prefilter: bool,
13017    preserve_alias: bool,
13018    allow_compatible_foreign_import: bool,
13019    scope_cache: Option<&LexicalScopeCache>,
13020) -> LexicalTypeResolution {
13021    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13022    let lexical_scope_started = Instant::now();
13023    if report_stats {
13024        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
13025    }
13026    let lexical_scope = if global {
13027        Vec::new()
13028    } else {
13029        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
13030            node,
13031            analyzer,
13032            visibility,
13033            file,
13034            source,
13035            true,
13036            recovered_macro_decorated_declarator_type(node)
13037                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
13038            scope_cache,
13039        ) {
13040            LexicalScopeResolution::Resolved(scope) => scope,
13041            LexicalScopeResolution::Ambiguous => {
13042                if report_stats {
13043                    eprintln!(
13044                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
13045                        lexical_scope_started.elapsed().as_millis(),
13046                    );
13047                }
13048                return LexicalTypeResolution::Ambiguous;
13049            }
13050            LexicalScopeResolution::Missing => {
13051                if report_stats {
13052                    eprintln!(
13053                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
13054                        lexical_scope_started.elapsed().as_millis(),
13055                    );
13056                }
13057                return LexicalTypeResolution::Missing;
13058            }
13059        }
13060    };
13061    if report_stats {
13062        eprintln!(
13063            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
13064            lexical_scope.len(),
13065            lexical_scope_started.elapsed().as_millis(),
13066        );
13067    }
13068    resolve_type_components_lexically_at_scoped(
13069        node,
13070        components,
13071        global,
13072        analyzer,
13073        visibility,
13074        ordinary_type_imports,
13075        file,
13076        source,
13077        direct_target,
13078        apply_structured_prefilter,
13079        preserve_alias,
13080        allow_compatible_foreign_import,
13081        lexical_scope,
13082    )
13083}
13084
13085#[allow(clippy::too_many_arguments)]
13086fn resolve_type_components_lexically_at_scoped(
13087    node: Node<'_>,
13088    components: &[String],
13089    global: bool,
13090    analyzer: &CppGraphSource<'_>,
13091    visibility: &VisibilityIndex<'_>,
13092    ordinary_type_imports: &OrdinaryTypeImportCell,
13093    file: &ProjectFile,
13094    source: &str,
13095    direct_target: Option<&CodeUnit>,
13096    apply_structured_prefilter: bool,
13097    preserve_alias: bool,
13098    allow_compatible_foreign_import: bool,
13099    mut lexical_scope: Vec<String>,
13100) -> LexicalTypeResolution {
13101    if !global
13102        && components.len() == 1
13103        // The two constant-time conditions run before the ancestor climb: each
13104        // `Node::parent` step re-descends from the root (#1927).
13105        && let Some(target) = direct_target
13106        && lexical_scope
13107            .last()
13108            .is_none_or(|last| last != &components[0])
13109        // A recovered class may contain a real member function nested inside
13110        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
13111        // XMLConstHandle). The nearest function_definition is then the member
13112        // itself, so inspect the complete ancestor chain.
13113        && has_malformed_wrapper_function_definition_ancestor(node)
13114        && let Some(indexed_namespace) =
13115            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
13116        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
13117    {
13118        lexical_scope = indexed_namespace;
13119    }
13120    resolve_type_components_in_authoritative_scope(
13121        node,
13122        components,
13123        global,
13124        analyzer,
13125        visibility,
13126        ordinary_type_imports,
13127        file,
13128        source,
13129        direct_target,
13130        apply_structured_prefilter,
13131        preserve_alias,
13132        allow_compatible_foreign_import,
13133        lexical_scope,
13134    )
13135}
13136
13137/// Resolve within a scope already proven by recovered syntax.
13138///
13139/// Unlike parser-derived scope, this scope must not be replaced with the
13140/// queried target's namespace: doing so would let target guidance override a
13141/// nearer declaration represented by the recovered syntax.
13142///
13143/// A qualifier that names a namespace alias in effect at `node` is rebound
13144/// here, once, for forward and inverse lookup alike: the rebound name is
13145/// looked up from the scope of the alias that supplies it. An ordinary name
13146/// the written spelling reaches at a deeper lexical tier shadows the alias,
13147/// and an alias whose target cannot be established resolves to nothing.
13148#[allow(clippy::too_many_arguments)]
13149pub(super) fn resolve_type_components_in_authoritative_scope(
13150    node: Node<'_>,
13151    components: &[String],
13152    global: bool,
13153    analyzer: &CppGraphSource<'_>,
13154    visibility: &VisibilityIndex<'_>,
13155    ordinary_type_imports: &OrdinaryTypeImportCell,
13156    file: &ProjectFile,
13157    source: &str,
13158    direct_target: Option<&CodeUnit>,
13159    apply_structured_prefilter: bool,
13160    preserve_alias: bool,
13161    allow_compatible_foreign_import: bool,
13162    lexical_scope: Vec<String>,
13163) -> LexicalTypeResolution {
13164    // Block scope is the innermost scope of unqualified lookup, so a local
13165    // class answers before every namespace, member and import tier below.
13166    if !global
13167        && let Some(resolution) =
13168            function_local_type_resolution(analyzer, file, node, components, source)
13169    {
13170        return resolution;
13171    }
13172    let resolve = |components: &[String], global: bool, lexical_scope: Vec<String>| {
13173        resolve_unaliased_type_components_in_authoritative_scope(
13174            node,
13175            components,
13176            global,
13177            analyzer,
13178            visibility,
13179            ordinary_type_imports,
13180            file,
13181            source,
13182            direct_target,
13183            apply_structured_prefilter,
13184            preserve_alias,
13185            allow_compatible_foreign_import,
13186            lexical_scope,
13187        )
13188    };
13189    let binding =
13190        visibility.bind_namespace_qualifier(file, node, components, global, &lexical_scope);
13191    let Some(depth) = binding.depth() else {
13192        return resolve(components, global, lexical_scope);
13193    };
13194    if let LexicalTypeResolution::Resolved {
13195        components: resolved,
13196        ..
13197    } = visibility.resolve_type_components_lexically(
13198        analyzer,
13199        file,
13200        components,
13201        global,
13202        &lexical_scope,
13203    ) && resolved.len().saturating_sub(components.len()) > depth
13204    {
13205        return resolve(components, global, lexical_scope);
13206    }
13207    match binding {
13208        NamespaceQualifierBinding::Bound {
13209            components,
13210            global,
13211            lexical_scope,
13212            ..
13213        } => resolve(&components, global, lexical_scope),
13214        NamespaceQualifierBinding::Unresolved { .. } => LexicalTypeResolution::Ambiguous,
13215        NamespaceQualifierBinding::Unaliased => unreachable!("an unaliased binding has no depth"),
13216    }
13217}
13218
13219/// Select conditional alias arms with the reference's guards before
13220/// canonicalizing them (#6902).
13221///
13222/// A resolved answer carries the arms as its candidates. An ambiguity carries
13223/// none, so the alias-preserving lookup at the same scope recovers them. A
13224/// proven selection resolves to its one type; undecided arms stay ambiguous
13225/// rather than naming either class.
13226#[allow(clippy::too_many_arguments)]
13227fn guarded_alias_type_resolution(
13228    node: Node<'_>,
13229    components: &[String],
13230    global: bool,
13231    lexical_scope: &[String],
13232    resolution: LexicalTypeResolution,
13233    analyzer: &CppGraphSource<'_>,
13234    visibility: &VisibilityIndex<'_>,
13235    file: &ProjectFile,
13236) -> LexicalTypeResolution {
13237    let preserved;
13238    let arms = match &resolution {
13239        LexicalTypeResolution::Resolved { .. } => &resolution,
13240        LexicalTypeResolution::Ambiguous => {
13241            preserved = visibility.resolve_type_components_lexically_for_forward(
13242                analyzer,
13243                file,
13244                components,
13245                global,
13246                lexical_scope,
13247            );
13248            &preserved
13249        }
13250        LexicalTypeResolution::Missing => return resolution,
13251    };
13252    let LexicalTypeResolution::Resolved {
13253        components: resolved_components,
13254        candidates,
13255        ..
13256    } = arms
13257    else {
13258        return resolution;
13259    };
13260    if !candidates
13261        .iter()
13262        .any(|candidate| declared_type_alias(analyzer, candidate))
13263    {
13264        return resolution;
13265    }
13266    match visibility.guarded_alias_selection_at_reference(analyzer, file, node, candidates) {
13267        None => resolution,
13268        Some(GuardedAliasSelection::Proven { unit, candidates }) => {
13269            LexicalTypeResolution::Resolved {
13270                unit,
13271                components: resolved_components.clone(),
13272                candidates,
13273            }
13274        }
13275        Some(GuardedAliasSelection::Alternatives(_)) => LexicalTypeResolution::Ambiguous,
13276    }
13277}
13278
13279#[allow(clippy::too_many_arguments)]
13280fn resolve_unaliased_type_components_in_authoritative_scope(
13281    node: Node<'_>,
13282    components: &[String],
13283    global: bool,
13284    analyzer: &CppGraphSource<'_>,
13285    visibility: &VisibilityIndex<'_>,
13286    ordinary_type_imports: &OrdinaryTypeImportCell,
13287    file: &ProjectFile,
13288    source: &str,
13289    direct_target: Option<&CodeUnit>,
13290    apply_structured_prefilter: bool,
13291    preserve_alias: bool,
13292    allow_compatible_foreign_import: bool,
13293    lexical_scope: Vec<String>,
13294) -> LexicalTypeResolution {
13295    if apply_structured_prefilter
13296        && direct_target.is_some()
13297        && !preserve_alias
13298        && !global
13299        && components.len() == 1
13300        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
13301    {
13302        return LexicalTypeResolution::Missing;
13303    }
13304    // A recovered macro-prefixed return type can share its global spelling
13305    // with aliases from mutually exclusive included headers. C++ lookup uses
13306    // the declaration physically present earlier in this file; the visibility
13307    // index deliberately retains every configuration alternative. Restore
13308    // that precedence only for the exact macro-decorated head (recovered by
13309    // the older grammar, or its clean macro-declaration reading) and lexical
13310    // tier.
13311    if !global
13312        && components.len() == 1
13313        && (recovered_macro_decorated_type_node(node).is_some()
13314            || macro_modifier_decorated_type_node(node))
13315        && let Some(resolution) = recovered_same_file_type_alias_resolution(
13316            node,
13317            components,
13318            analyzer,
13319            visibility,
13320            file,
13321            direct_target,
13322            &lexical_scope,
13323        )
13324    {
13325        return resolution;
13326    }
13327    if apply_structured_prefilter
13328        && let Some(target) = direct_target
13329        && !preserve_alias
13330        && !visibility.structured_type_reference_may_resolve_to_target(
13331            analyzer,
13332            file,
13333            components,
13334            global,
13335            &lexical_scope,
13336            target,
13337        )
13338    {
13339        return LexicalTypeResolution::Missing;
13340    }
13341    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
13342    let lexical_normal_started = Instant::now();
13343    if report_stats {
13344        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
13345    }
13346    let normal = if preserve_alias {
13347        visibility.resolve_type_components_lexically_for_forward(
13348            analyzer,
13349            file,
13350            components,
13351            global,
13352            &lexical_scope,
13353        )
13354    } else {
13355        direct_target.map_or_else(
13356            || {
13357                visibility.resolve_type_components_lexically(
13358                    analyzer,
13359                    file,
13360                    components,
13361                    global,
13362                    &lexical_scope,
13363                )
13364            },
13365            |target| {
13366                visibility.resolve_type_components_lexically_for_target(
13367                    analyzer,
13368                    file,
13369                    components,
13370                    global,
13371                    &lexical_scope,
13372                    target,
13373                )
13374            },
13375        )
13376    };
13377    if report_stats {
13378        eprintln!(
13379            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
13380            lexical_normal_started.elapsed().as_millis(),
13381        );
13382    }
13383    // Conditional alias arms face this reference's guards before their targets
13384    // are canonicalized together (#6902). The preserving forward mode keeps the
13385    // alias itself, so it has nothing to select.
13386    let normal = if preserve_alias || cpp_template_reference_arguments(node, source).is_some() {
13387        normal
13388    } else {
13389        guarded_alias_type_resolution(
13390            node,
13391            components,
13392            global,
13393            &lexical_scope,
13394            normal,
13395            analyzer,
13396            visibility,
13397            file,
13398        )
13399    };
13400    let normal = match normal {
13401        LexicalTypeResolution::Resolved { ref unit, .. }
13402            if !visibility
13403                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
13404                && !direct_target.is_some_and(|target| {
13405                    let candidate_refs = visibility
13406                        .visible_identifier_candidates(file, target.identifier())
13407                        .collect::<Vec<_>>();
13408                    visibility.c_tag_declaration_family_matches_target(
13409                        analyzer,
13410                        file,
13411                        &candidate_refs,
13412                        target,
13413                    )
13414                }) =>
13415        {
13416            LexicalTypeResolution::Missing
13417        }
13418        resolution => resolution,
13419    };
13420    let normal_depth = match &normal {
13421        LexicalTypeResolution::Resolved { components, .. } => {
13422            Some(components.len().saturating_sub(1))
13423        }
13424        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
13425    };
13426    // Ordinary using-declarations participate in unqualified lookup at their
13427    // lexical scope. They therefore replace the resolver's terminal/global
13428    // fallback at the same or a shallower depth. A declaration in a more deeply
13429    // nested named scope is the closer lexical result and remains authoritative.
13430    // Ambiguous imports fail closed unless such a closer declaration exists.
13431    let ordinary_import_started = Instant::now();
13432    if report_stats {
13433        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
13434    }
13435    let ordinary_import_resolution = ordinary_type_import_resolution(
13436        node,
13437        components,
13438        global,
13439        analyzer,
13440        visibility,
13441        ordinary_type_imports,
13442        file,
13443        source,
13444        &lexical_scope,
13445        direct_target,
13446    );
13447    if report_stats {
13448        eprintln!(
13449            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
13450            ordinary_import_started.elapsed().as_millis(),
13451        );
13452    }
13453    let resolution = match ordinary_import_resolution {
13454        OrdinaryTypeImportResolution::Missing => normal,
13455        OrdinaryTypeImportResolution::Resolved {
13456            lexical_depth,
13457            is_direct,
13458            ..
13459        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
13460            || normal_depth.is_some_and(|depth| {
13461                depth > lexical_depth || (!is_direct && depth == lexical_depth)
13462            }) =>
13463        {
13464            normal
13465        }
13466        OrdinaryTypeImportResolution::Resolved {
13467            target,
13468            target_components,
13469            ..
13470        } => visibility.resolve_imported_type_candidate(
13471            analyzer,
13472            file,
13473            &target,
13474            &target_components,
13475            direct_target,
13476            preserve_alias,
13477        ),
13478        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
13479            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
13480        {
13481            normal
13482        }
13483        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13484    };
13485    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
13486        return resolution;
13487    }
13488
13489    // A foreign header can contribute an exact using binding from a build
13490    // configuration that is compatible with, but not implied by, the current
13491    // reference. Forward navigation has no "unproven" result channel, so it
13492    // may still navigate through one unique structured import after ordinary
13493    // lookup misses. Inverse attribution keeps using its target-guided path
13494    // and records the same evidence as unproven (#940).
13495    match compatible_foreign_type_import_resolution(
13496        node,
13497        components,
13498        global,
13499        analyzer,
13500        visibility,
13501        ordinary_type_imports,
13502        file,
13503        source,
13504        &lexical_scope,
13505        None,
13506    ) {
13507        OrdinaryTypeImportResolution::Missing => resolution,
13508        OrdinaryTypeImportResolution::Resolved {
13509            target,
13510            target_components,
13511            ..
13512        } => visibility.resolve_imported_type_candidate(
13513            analyzer,
13514            file,
13515            &target,
13516            &target_components,
13517            None,
13518            true,
13519        ),
13520        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
13521    }
13522}
13523
13524fn recovered_same_file_type_alias_resolution(
13525    node: Node<'_>,
13526    components: &[String],
13527    analyzer: &CppGraphSource<'_>,
13528    visibility: &VisibilityIndex<'_>,
13529    file: &ProjectFile,
13530    direct_target: Option<&CodeUnit>,
13531    lexical_scope: &[String],
13532) -> Option<LexicalTypeResolution> {
13533    debug_assert_eq!(components.len(), 1);
13534    debug_assert!(
13535        recovered_macro_decorated_type_node(node).is_some()
13536            || macro_modifier_decorated_type_node(node)
13537    );
13538    let alias_provider = analyzer.type_alias_provider()?;
13539    for qualified in lexical_component_tiers(components, false, lexical_scope) {
13540        let candidates = visibility
13541            .visible_identifier_candidates(file, &components[0])
13542            .filter(|candidate| {
13543                candidate.source() == file
13544                    && canonical_cpp_scope_components(candidate) == qualified
13545                    && visibility
13546                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
13547            })
13548            .collect::<Vec<_>>();
13549        if candidates.is_empty() {
13550            continue;
13551        }
13552        if candidates
13553            .iter()
13554            .any(|candidate| !alias_provider.is_type_alias(candidate))
13555        {
13556            return None;
13557        }
13558        let unit = if let Some(target) = direct_target {
13559            visibility.unique_type_candidate_preserving_target(
13560                analyzer,
13561                file,
13562                &candidates,
13563                target,
13564            )?
13565        } else {
13566            let first = candidates[0];
13567            if candidates
13568                .iter()
13569                .any(|candidate| !same_visible_symbol(candidate, first))
13570            {
13571                return None;
13572            }
13573            first.clone()
13574        };
13575        return Some(LexicalTypeResolution::Resolved {
13576            unit,
13577            components: qualified,
13578            candidates: candidates.into_iter().cloned().collect(),
13579        });
13580    }
13581    None
13582}
13583
13584fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13585    matches!(
13586        structured_owner_context_resolution(node, ctx),
13587        StructuredOwnerContextResolution::SelfTarget
13588            | StructuredOwnerContextResolution::InheritedTarget
13589    )
13590}
13591
13592/// A bare/`this->` member call whose name resolves, through the enclosing class's base
13593/// hierarchy or through a lexically enclosing class, to the target member declared on
13594/// another owner. This is a genuine external usage of that owner's member rather than a
13595/// same-type self call.
13596fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13597    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
13598        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
13599    }) else {
13600        return matches!(
13601            structured_owner_context_resolution(node, ctx),
13602            StructuredOwnerContextResolution::InheritedTarget
13603        );
13604    };
13605    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13606        return false;
13607    };
13608    let chain = structured_enclosing_owner_chain(node, ctx);
13609    let Some(innermost) = chain.first() else {
13610        return false;
13611    };
13612    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13613        return false;
13614    }
13615    let Some(arity) = ctx
13616        .visibility
13617        .call_arity_evidence(ctx.file, call, ctx.source)
13618        .exact()
13619    else {
13620        return false;
13621    };
13622    for enclosing_owner in &chain {
13623        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13624            return true;
13625        }
13626        match resolve_declaring_callable_owner(
13627            &ctx.analyzer,
13628            ctx.visibility,
13629            ctx.file,
13630            cached_declaring_member_owner(enclosing_owner, ctx),
13631            &ctx.spec.member_name,
13632            arity,
13633        ) {
13634            EnclosingMemberOwnerResolution::Owner(owner) => {
13635                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13636            }
13637            EnclosingMemberOwnerResolution::Ambiguous => return false,
13638            // The name is not declared in this class or its bases, so C++
13639            // unqualified lookup continues in the next enclosing class (#3095).
13640            EnclosingMemberOwnerResolution::Missing => {}
13641        }
13642    }
13643    false
13644}
13645
13646fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13647    matches!(
13648        structured_owner_context_resolution(node, ctx),
13649        StructuredOwnerContextResolution::NonTarget
13650    )
13651}
13652
13653fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13654    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13655        return false;
13656    };
13657    let mut current = ctx.ancestry.parent(node);
13658    while let Some(parent) = current {
13659        if parent.kind() == "function_definition" {
13660            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
13661                return false;
13662            };
13663            if let Some(owners) = out_of_line_member_definition_owner(
13664                &ctx.analyzer,
13665                ctx.visibility,
13666                ctx.file,
13667                ctx.source,
13668                owner_lookup,
13669            ) && let Some((_, owner)) = owners.innermost()
13670            {
13671                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
13672            }
13673            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
13674                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13675            }
13676            return false;
13677        }
13678        current = ctx.ancestry.parent(parent);
13679    }
13680    false
13681}
13682
13683#[derive(Clone, Copy)]
13684enum StructuredOwnerContextResolution {
13685    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
13686    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
13687    SelfTarget,
13688    /// The enclosing class does not declare the member; lookup reaches the target owner
13689    /// through a base of that class, or through a lexically enclosing class (a nested
13690    /// class calling the outer class's member). Either way the reference is a genuine
13691    /// external usage OF that owner's member (e.g. `Derived` calling inherited
13692    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
13693    InheritedTarget,
13694    NonTarget,
13695    Ambiguous,
13696    Missing,
13697}
13698
13699fn structured_owner_context_resolution(
13700    node: Node<'_>,
13701    ctx: &ScanCtx<'_>,
13702) -> StructuredOwnerContextResolution {
13703    let Some(target_owner) = ctx.spec.owner.as_ref() else {
13704        return StructuredOwnerContextResolution::Missing;
13705    };
13706    let chain = structured_enclosing_owner_chain(node, ctx);
13707    let Some(innermost) = chain.first() else {
13708        return StructuredOwnerContextResolution::Missing;
13709    };
13710    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13711        return StructuredOwnerContextResolution::SelfTarget;
13712    }
13713    // Unqualified lookup searches the enclosing class and its bases, then each lexically
13714    // enclosing class outward, and stops at the first scope that declares the name (#3095).
13715    // Only the immediately enclosing class makes a reference a same-type self call; any
13716    // other owner it reaches is a genuine usage of that owner's member.
13717    for enclosing_owner in &chain {
13718        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13719            return StructuredOwnerContextResolution::InheritedTarget;
13720        }
13721        match cached_declaring_member_owner(enclosing_owner, ctx) {
13722            EnclosingMemberOwnerResolution::Owner(owner)
13723                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
13724            {
13725                return StructuredOwnerContextResolution::InheritedTarget;
13726            }
13727            EnclosingMemberOwnerResolution::Owner(_) => {
13728                return StructuredOwnerContextResolution::NonTarget;
13729            }
13730            EnclosingMemberOwnerResolution::Ambiguous => {
13731                return StructuredOwnerContextResolution::Ambiguous;
13732            }
13733            EnclosingMemberOwnerResolution::Missing => {}
13734        }
13735    }
13736    StructuredOwnerContextResolution::Missing
13737}
13738
13739fn cached_declaring_member_owner(
13740    receiver_owner: &CodeUnit,
13741    ctx: &ScanCtx<'_>,
13742) -> EnclosingMemberOwnerResolution {
13743    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
13744        return cached;
13745    }
13746    let resolved = resolve_declaring_member_owner(
13747        &ctx.analyzer,
13748        ctx.visibility,
13749        ctx.file,
13750        receiver_owner,
13751        &ctx.spec.member_name,
13752    );
13753    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13754        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13755    } else {
13756        resolved
13757    };
13758    ctx.member_owner_cache
13759        .borrow_mut()
13760        .insert(receiver_owner.clone(), resolved.clone());
13761    resolved
13762}
13763
13764/// Recover a member's declaring owner when parser recovery omitted its
13765/// in-class declaration but retained an out-of-line definition. Ordinary
13766/// visible-member lookup runs first. The structured definition index then
13767/// supplies the missing member fact at each hierarchy level, so an indexed
13768/// derived override still hides the queried base member and distinct base
13769/// paths still fail closed.
13770fn indexed_declaring_owner_for_recovered_member(
13771    receiver_owner: &CodeUnit,
13772    ctx: &ScanCtx<'_>,
13773) -> EnclosingMemberOwnerResolution {
13774    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13775        return EnclosingMemberOwnerResolution::Missing;
13776    };
13777    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13778        return EnclosingMemberOwnerResolution::Missing;
13779    }
13780    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13781        return EnclosingMemberOwnerResolution::Missing;
13782    };
13783    let Some(receiver_owner) =
13784        ctx.visibility
13785            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13786    else {
13787        return EnclosingMemberOwnerResolution::Ambiguous;
13788    };
13789    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13790        ctx.visibility
13791            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13792    }) else {
13793        return EnclosingMemberOwnerResolution::Missing;
13794    };
13795
13796    let owner_declares_member = |owner: &CodeUnit| {
13797        if same_visible_symbol(owner, &target_owner) {
13798            return true;
13799        }
13800        let mut member_fq = owner.fq().clone();
13801        member_fq.push(
13802            ctx.spec
13803                .target
13804                .fq()
13805                .last()
13806                .expect("a method target has a terminal member segment"),
13807        );
13808        ctx.analyzer
13809            .definitions(&member_fq.display(segment_interner()))
13810            .any(|child| child.is_function())
13811    };
13812    if owner_declares_member(&receiver_owner) {
13813        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13814    }
13815
13816    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
13817    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13818    let mut declaring_owner = None;
13819    while let Some(raw_owner) = stack.pop() {
13820        let Some(owner) =
13821            ctx.visibility
13822                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13823        else {
13824            return EnclosingMemberOwnerResolution::Ambiguous;
13825        };
13826        let propagated = propagated_counts.entry(owner.clone()).or_default();
13827        if *propagated == 2 {
13828            continue;
13829        }
13830        *propagated += 1;
13831        if owner_declares_member(&owner) {
13832            if declaring_owner.is_some() {
13833                return EnclosingMemberOwnerResolution::Ambiguous;
13834            }
13835            declaring_owner = Some(owner);
13836            continue;
13837        }
13838        stack.extend(hierarchy.get_direct_ancestors(&owner));
13839    }
13840    declaring_owner
13841        .map(EnclosingMemberOwnerResolution::Owner)
13842        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13843}
13844
13845/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
13846/// lookup searches each in turn before it reaches the enclosing namespaces.
13847fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13848    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13849        return Vec::new();
13850    };
13851    // The innermost owner is whatever enclosing-owner resolution produced, so
13852    // it is kept as given; only the walk outward is filtered to classes.
13853    brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13854        ctx.analyzer.parent_of(unit)
13855    })
13856    .collect()
13857}
13858
13859fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13860    // Declaration recovery can index the true class/member ranges even when
13861    // the original error tree wraps that region in a bogus function. Prefer
13862    // the analyzer's exact enclosing-owner graph at the reference byte before
13863    // interpreting such a wrapper as a real callable owner.
13864    if (has_recovered_class_shape_ancestor(node)
13865        || has_malformed_wrapper_function_definition_ancestor(node))
13866        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13867    {
13868        return Some(owner);
13869    }
13870    let mut current = ctx.ancestry.parent(node);
13871    while let Some(parent) = current {
13872        if parent.kind() == "function_definition" {
13873            let owner_lookup = function_definition_owner_lookup_node(parent);
13874            if let Some(owner_lookup) = owner_lookup
13875                && let Some(owners) = out_of_line_member_definition_owner(
13876                    &ctx.analyzer,
13877                    ctx.visibility,
13878                    ctx.file,
13879                    ctx.source,
13880                    owner_lookup,
13881                )
13882                && let Some((_, owner)) = owners.innermost()
13883            {
13884                return Some(owner.clone());
13885            }
13886            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13887                return Some(owner);
13888            }
13889            if let Some(owner) = enclosing_context(parent, ctx)
13890                .owner
13891                .filter(|owner| owner.is_class())
13892            {
13893                return Some(owner);
13894            }
13895            if let Some(owner_lookup) = owner_lookup
13896                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13897            {
13898                return Some(owner);
13899            }
13900            break;
13901        }
13902        current = ctx.ancestry.parent(parent);
13903    }
13904    enclosing_context(node, ctx)
13905        .owner
13906        .filter(|owner| owner.is_class())
13907}
13908
13909fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13910    let target_owner = ctx.spec.owner.as_ref()?;
13911    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13912    let owner_name = owner_components.last()?;
13913    let mut candidates = Vec::new();
13914    for candidate in ctx
13915        .visibility
13916        .visible_identifier_candidates(ctx.file, owner_name)
13917        .filter(|candidate| candidate.is_class())
13918    {
13919        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13920            brokk_bifrost_core::analyzer::Language::Cpp,
13921            &cpp_name_for(candidate),
13922        );
13923        if !components.ends_with(&owner_components)
13924            || candidates
13925                .iter()
13926                .any(|existing| same_logical_symbol(existing, candidate))
13927        {
13928            continue;
13929        }
13930        candidates.push(candidate.clone());
13931    }
13932    let [candidate] = candidates.as_slice() else {
13933        return None;
13934    };
13935    (same_logical_symbol(candidate, target_owner)
13936        && target_group_contains_owner_peer(candidate, ctx))
13937    .then(|| candidate.clone())
13938}