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