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((
9833                            parent.start_byte(),
9834                            components,
9835                            namespace_definition_has_real_braces(parent),
9836                        ));
9837                    }
9838                }
9839            }
9840            "class_specifier" | "struct_specifier" | "union_specifier" => {
9841                if let Some(name) = parent.child_by_field_name("name") {
9842                    let mut components = Vec::new();
9843                    if append_cpp_name_components(name, source, &mut components).is_some() {
9844                        classes.push(components);
9845                    }
9846                }
9847            }
9848            "function_definition" => {
9849                if function_definition.is_none() {
9850                    function_definition = Some(parent);
9851                }
9852                displaced_class_scope = displaced_class_scope
9853                    || parent.child_by_field_name("type").is_some_and(|type_node| {
9854                        matches!(
9855                            type_node.kind(),
9856                            "class_specifier" | "struct_specifier" | "union_specifier"
9857                        )
9858                    })
9859                    || is_malformed_wrapper_function_definition(parent);
9860            }
9861            _ => {}
9862        }
9863        current = parent.parent();
9864    }
9865    namespaces.reverse();
9866    // The parsed chain can stop short of the namespaces tree-sitter's error
9867    // recovery closed early; the orphaned index restores them (#1537).
9868    let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
9869    let mut scope = namespace.clone();
9870    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
9871    // qualified function declarator. It is recovery scaffolding, not a real
9872    // callable owner, and must not overwrite the indexed class scope retained
9873    // by the wrapper body (#2249).
9874    let has_qualified_function_owner = function_definition
9875        .filter(|function| !is_malformed_wrapper_function_definition(*function))
9876        .and_then(function_definition_owner_lookup_node)
9877        .is_some_and(|owner| {
9878            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
9879        });
9880    let indexed_scope = displaced_class_scope
9881        .then(|| {
9882            indexed_structural_class_scope(visibility, file, node, source)
9883                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9884        })
9885        .flatten()
9886        .or_else(|| {
9887            // A qualified out-of-line definition can lose its class owner from
9888            // the parser tree when a namespace sentinel or export macro wraps
9889            // the declaration.  Recover the indexed owner scope up front so
9890            // all unqualified type references in the body see the same class
9891            // boundary as C++ lookup, including aliases in parameters and
9892            // local declarations (not only template-argument leaves).
9893            (has_qualified_function_owner && function_definition.is_some())
9894                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9895                .flatten()
9896                .filter(|indexed| {
9897                    qualified_owner_scope_is_recoverable(
9898                        indexed,
9899                        &namespace,
9900                        &classes,
9901                        function_definition
9902                            .and_then(function_definition_owner_lookup_node)
9903                            .and_then(|owner| qualified_callable_owner_components(owner, source))
9904                            .map(|(components, _)| components),
9905                    )
9906                })
9907        })
9908        .or_else(|| {
9909            // A nested class declaration can likewise lose one of its outer
9910            // class ancestors from the CST.  Prefer the exact indexed
9911            // structural class scope. Its same-file declaration range and
9912            // exact template-id match already prove the owner, including a
9913            // specialization whose parser component retains only the primary
9914            // name. Keep the suffix guard for graph-only recovery, which lacks
9915            // that direct syntax-range proof.
9916            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
9917                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
9918                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
9919                })
9920            })
9921        })
9922        .or_else(|| {
9923            // Retain the existing indexed lexical-scope recovery for
9924            // unqualified function bodies.  It is intentionally last so a
9925            // canonical class owner wins whenever one is available.
9926            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
9927                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
9928                .flatten()
9929                .filter(|indexed| indexed.len() > namespace.len())
9930        });
9931    if let Some(indexed_scope) = indexed_scope.as_ref() {
9932        // A macro-displaced namespace can leave the parser with the real class
9933        // body but no namespace ancestor. Prefer the structural class match;
9934        // partial specializations whose structured name cannot round-trip use
9935        // the exact indexed enclosing-owner chain instead.
9936        scope = indexed_scope.clone();
9937        classes.clear();
9938    }
9939
9940    if !ignore_function_owner
9941        && has_qualified_function_owner
9942        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
9943    {
9944        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
9945            return LexicalScopeResolution::Missing;
9946        };
9947        // Resolve the out-of-line owner from the parser namespace before the
9948        // provisional indexed parent can influence the answer. Per-file
9949        // extraction can assign the first same-depth using namespace to a
9950        // bare owner. The structured using resolver instead selects the
9951        // namespace whose visible class has the owner name (#1838).
9952        let imports = visibility.ordinary_type_import_cell(file);
9953        let owner_resolution = resolve_type_components_lexically_at_scoped(
9954            function,
9955            &owner,
9956            global,
9957            analyzer,
9958            visibility,
9959            &imports,
9960            file,
9961            source,
9962            None,
9963            false,
9964            false,
9965            false,
9966            namespace.clone(),
9967        );
9968        match owner_resolution {
9969            LexicalTypeResolution::Resolved {
9970                unit, components, ..
9971            } if is_indexed_class_owner(analyzer, &unit) => {
9972                scope = components;
9973                classes.clear();
9974            }
9975            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
9976            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
9977                match visibility
9978                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
9979                {
9980                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
9981                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
9982                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
9983                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
9984                            qualified_owner_scope_is_recoverable(
9985                                indexed,
9986                                &namespace,
9987                                &classes,
9988                                Some(owner.clone()),
9989                            )
9990                        }) {
9991                            scope = indexed.clone();
9992                        } else {
9993                            scope = if global || owner.starts_with(&namespace) {
9994                                owner
9995                            } else {
9996                                let mut relative = namespace;
9997                                relative.extend(owner);
9998                                relative
9999                            };
10000                        }
10001                    }
10002                    LexicalTypeResolution::Missing => {
10003                        // Structural lexical resolution cannot see an owner class that
10004                        // is reachable only through an in-scope `using namespace`
10005                        // directive, so it would otherwise hard-fail here. The indexed
10006                        // definition already carries the true fully-qualified owner
10007                        // (its package reflects the directive), so recover the real
10008                        // enclosing scope from the analyzer graph -- exactly the scope
10009                        // chain real C++ unqualified lookup traverses. Only the strict
10010                        // callers reach this arm; the best-effort callers above keep
10011                        // their existing structural guess (and its query profile).
10012                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10013                            .or_else(|| {
10014                                indexed_namespace_qualified_scope(
10015                                    analyzer, visibility, file, node, &owner,
10016                                )
10017                            }) {
10018                            Some(indexed) => scope = indexed,
10019                            None => return LexicalScopeResolution::Missing,
10020                        }
10021                    }
10022                }
10023            }
10024        }
10025    }
10026
10027    classes.reverse();
10028    scope.extend(classes.into_iter().flatten());
10029    LexicalScopeResolution::Resolved(scope)
10030}
10031
10032fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10033    let mut current = node.parent();
10034    while let Some(parent) = current {
10035        if parent.kind() == "function_definition"
10036            && parent.child_by_field_name("type").is_some_and(|type_node| {
10037                matches!(
10038                    type_node.kind(),
10039                    "class_specifier" | "struct_specifier" | "union_specifier"
10040                )
10041            })
10042        {
10043            return true;
10044        }
10045        current = parent.parent();
10046    }
10047    false
10048}
10049
10050fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10051    let mut current = node.parent();
10052    while let Some(parent) = current {
10053        if parent.kind() == "function_definition"
10054            && is_malformed_wrapper_function_definition(parent)
10055        {
10056            return true;
10057        }
10058        current = parent.parent();
10059    }
10060    false
10061}
10062
10063fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10064    node.has_error()
10065        && node
10066            .child_by_field_name("declarator")
10067            .is_some_and(|declarator| {
10068                declarator.kind() != "function_declarator"
10069                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
10070            })
10071}
10072
10073/// Tree-sitter can make an attribute/nullability macro look like the namespace
10074/// component of a qualified function owner when it appears between the return
10075/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
10076/// The recovered owner is not a C++ lexical owner, so callers resolving the
10077/// ordinary return/parameter type must retain the surrounding namespace scope.
10078fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10079    node.child_by_field_name("scope")
10080        .and_then(|scope| recovered_macro_decorated_type_node(scope))
10081        .is_some()
10082}
10083
10084fn indexed_structural_class_scope(
10085    visibility: &VisibilityIndex<'_>,
10086    file: &ProjectFile,
10087    node: Node<'_>,
10088    source: &str,
10089) -> Option<Vec<String>> {
10090    let mut current = node.parent();
10091    while let Some(parent) = current {
10092        if matches!(
10093            parent.kind(),
10094            "class_specifier" | "struct_specifier" | "union_specifier"
10095        ) {
10096            return visibility.indexed_structural_class_scope(file, parent, source);
10097        }
10098        current = parent.parent();
10099    }
10100    None
10101}
10102
10103/// Check that an indexed owner scope is a structured completion of the parser
10104/// scope rather than an unrelated same-spelled declaration.
10105///
10106/// Error recovery around C++ namespace sentinels can preserve only a subset of
10107/// the namespace/class chain.  The indexed definition still carries the full
10108/// owner path, so require every surviving parser component to occur in order
10109/// and require any explicit qualified function owner to be the terminal
10110/// suffix.  An empty parser scope is accepted only with that qualified-owner
10111/// suffix evidence; a lone top-level short name is not evidence that a
10112/// namespace was lost.
10113fn qualified_owner_scope_is_recoverable(
10114    indexed: &[String],
10115    namespace: &[String],
10116    classes: &[Vec<String>],
10117    qualified_owner: Option<Vec<String>>,
10118) -> bool {
10119    if let Some(owner) = qualified_owner {
10120        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10121            return false;
10122        }
10123        // A malformed namespace sentinel can erase every parser namespace
10124        // ancestor.  The indexed enclosing callable still provides an
10125        // authoritative class owner, so the qualified owner suffix itself is
10126        // enough evidence in that case.  When namespace components survived,
10127        // retain the stricter subsequence check below.
10128        if namespace.is_empty() {
10129            return true;
10130        }
10131        if indexed.len() <= namespace.len() {
10132            return false;
10133        }
10134        let mut prefix = indexed.iter();
10135        return namespace
10136            .iter()
10137            .all(|component| prefix.any(|candidate| candidate == component));
10138    }
10139    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10140    if !class_components.is_empty() {
10141        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10142    }
10143    if namespace.is_empty() || indexed.len() <= namespace.len() {
10144        return false;
10145    }
10146    let mut prefix = indexed.iter();
10147    namespace
10148        .iter()
10149        .all(|component| prefix.any(|candidate| candidate == component))
10150}
10151
10152/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
10153/// counts as the true lexical owner recovered from the indexed graph.
10154fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10155    unit.is_class()
10156        && !analyzer
10157            .type_alias_provider()
10158            .is_some_and(|provider| provider.is_type_alias(unit))
10159}
10160
10161/// Recover the enclosing member's true lexical scope from the *indexed*
10162/// definition when structural resolution cannot see the owner class.
10163///
10164/// An out-of-line member defined at file scope (`int HTMLLayout::method()
10165/// {...}`) whose owner class is reachable only through an in-scope `using
10166/// namespace X;` directive cannot be resolved by `resolve_type_components_
10167/// lexically`, which walks structural lexical tiers and never consults
10168/// using-directives. The definition itself, however, is indexed with its true
10169/// fully-qualified identity (its package already reflects the directive), so
10170/// the analyzer graph knows the real owner. Walk from the reference's indexed
10171/// enclosing code unit up to the innermost enclosing class and return that
10172/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
10173/// -- exactly the scope chain C++ unqualified lookup traverses.
10174fn indexed_enclosing_owner_scope(
10175    analyzer: &CppGraphSource<'_>,
10176    visibility: &VisibilityIndex<'_>,
10177    file: &ProjectFile,
10178    node: Node<'_>,
10179) -> Option<Vec<String>> {
10180    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10181}
10182
10183/// Recover the lexical scope of an out-of-line definition whose syntactic
10184/// qualifier names a *namespace* rather than a class (`void out::target(int)
10185/// {...}` written at file scope, issue #3096).
10186///
10187/// `indexed_enclosing_owner_scope` looks for an enclosing *class*, so it finds
10188/// nothing here and the strict caller would fail closed for every unqualified
10189/// reference in the body. C++ unqualified lookup inside such a definition
10190/// proceeds from the named namespace outward, exactly as inside a `namespace
10191/// out { ... }` block, and the indexed definition already carries that scope
10192/// (`["out"]` for `out::target`).
10193///
10194/// Take it only when a namespace of that name is visible from the file and the
10195/// indexed scope ends with the syntactic qualifier. A qualifier naming a class
10196/// the graph does not hold (`void MissingContainer::call()`) has no such
10197/// namespace, so it still fails closed -- and it is rejected by the in-memory
10198/// visibility index, before any indexed enclosing-unit query.
10199fn indexed_namespace_qualified_scope(
10200    analyzer: &CppGraphSource<'_>,
10201    visibility: &VisibilityIndex<'_>,
10202    file: &ProjectFile,
10203    node: Node<'_>,
10204    owner: &[String],
10205) -> Option<Vec<String>> {
10206    let name = owner.last().expect("a qualified owner has one component");
10207    if !visibility
10208        .visible_identifier_candidates(file, name)
10209        .any(|unit| unit.is_module())
10210    {
10211        return None;
10212    }
10213    let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10214    indexed.ends_with(owner).then_some(indexed)
10215}
10216
10217fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10218    let start = enclosing_context(node, ctx).enclosing?;
10219    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10220        ctx.analyzer.parent_of(unit)
10221    })
10222    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10223}
10224
10225pub fn resolve_type_node_lexically(
10226    node: Node<'_>,
10227    analyzer: &CppGraphSource<'_>,
10228    visibility: &VisibilityIndex<'_>,
10229    ordinary_type_imports: &OrdinaryTypeImportCell,
10230    file: &ProjectFile,
10231    source: &str,
10232) -> LexicalTypeResolution {
10233    let Some((components, global)) = type_reference_components(node, source) else {
10234        return LexicalTypeResolution::Missing;
10235    };
10236    let resolution = resolve_type_components_lexically_at(
10237        node,
10238        &components,
10239        global,
10240        analyzer,
10241        visibility,
10242        ordinary_type_imports,
10243        file,
10244        source,
10245    );
10246    if !is_cpp_template_argument_type_leaf(node) {
10247        return resolution;
10248    }
10249
10250    // Error recovery can detach a member function from its class while
10251    // leaving an unqualified type argument (for example `error_type` in
10252    // `expected<..., error_type>`). The normal structural scope then lacks
10253    // the class owner and resolves the wrong same-spelled alias, or fails
10254    // closed. The indexed enclosing unit still carries the authoritative
10255    // class scope; retry only this narrowly-shaped leaf with that scope.
10256    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10257        return resolution;
10258    };
10259    let namespace_scope = enclosing_namespace_components(node, source);
10260    if indexed_scope.len() <= namespace_scope.len() {
10261        return resolution;
10262    }
10263    let indexed = visibility.resolve_type_components_lexically(
10264        analyzer,
10265        file,
10266        &components,
10267        global,
10268        &indexed_scope,
10269    );
10270    match indexed {
10271        LexicalTypeResolution::Resolved { ref unit, .. }
10272            if !visibility
10273                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10274        {
10275            resolution
10276        }
10277        LexicalTypeResolution::Resolved { .. } => indexed,
10278        _ => resolution,
10279    }
10280}
10281
10282#[allow(clippy::too_many_arguments)]
10283pub fn resolve_type_node_lexically_for_target(
10284    node: Node<'_>,
10285    analyzer: &CppGraphSource<'_>,
10286    visibility: &VisibilityIndex<'_>,
10287    ordinary_type_imports: &OrdinaryTypeImportCell,
10288    file: &ProjectFile,
10289    source: &str,
10290    target: &CodeUnit,
10291    scope_cache: Option<&LexicalScopeCache>,
10292    recovered_scope: Option<&[String]>,
10293) -> LexicalTypeResolution {
10294    let Some((reference_components, global)) = type_reference_components(node, source) else {
10295        return LexicalTypeResolution::Missing;
10296    };
10297    let terminal = reference_components
10298        .last()
10299        .expect("type reference components are non-empty");
10300    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
10301        return LexicalTypeResolution::Missing;
10302    }
10303    let template_arguments = cpp_template_reference_arguments(node, source);
10304    let selects_concrete_specialization =
10305        template_arguments.is_some() && visibility.is_template_specialization(target);
10306    if !selects_concrete_specialization
10307        && !visibility.structured_type_reference_may_resolve_to_target(
10308            analyzer,
10309            file,
10310            std::slice::from_ref(terminal),
10311            false,
10312            &[],
10313            target,
10314        )
10315    {
10316        return LexicalTypeResolution::Missing;
10317    }
10318    if let Some(arguments) = template_arguments.as_ref() {
10319        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
10320            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10321                node,
10322                &reference_components,
10323                global,
10324                analyzer,
10325                visibility,
10326                ordinary_type_imports,
10327                file,
10328                source,
10329                recovered_scope,
10330            )
10331        } else {
10332            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10333                node,
10334                &reference_components,
10335                global,
10336                analyzer,
10337                visibility,
10338                ordinary_type_imports,
10339                file,
10340                source,
10341                scope_cache,
10342            )
10343        };
10344        return match alias_resolution {
10345            LexicalTypeResolution::Resolved {
10346                unit,
10347                components,
10348                candidates,
10349            } if visibility.template_alias_arguments_preserve_target(
10350                analyzer, file, &unit, arguments, target,
10351            ) =>
10352            {
10353                LexicalTypeResolution::Resolved {
10354                    unit: target.clone(),
10355                    components,
10356                    candidates,
10357                }
10358            }
10359            LexicalTypeResolution::Resolved {
10360                unit,
10361                components,
10362                candidates,
10363            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
10364                Ok(resolved_unit) => {
10365                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
10366                        .then(|| {
10367                            target_guided_malformed_template_alias_resolution(
10368                                node,
10369                                analyzer,
10370                                visibility,
10371                                file,
10372                                arguments,
10373                                &reference_components,
10374                                target,
10375                            )
10376                        })
10377                        .flatten();
10378                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
10379                        unit: resolved_unit,
10380                        components,
10381                        candidates,
10382                    })
10383                }
10384                Err(_) => LexicalTypeResolution::Ambiguous,
10385            },
10386            LexicalTypeResolution::Missing => {
10387                let target_preserving = if let Some(recovered_scope) = recovered_scope {
10388                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
10389                        node,
10390                        &reference_components,
10391                        global,
10392                        analyzer,
10393                        visibility,
10394                        ordinary_type_imports,
10395                        file,
10396                        source,
10397                        target,
10398                        true,
10399                        recovered_scope,
10400                    )
10401                } else {
10402                    resolve_type_components_lexically_at_for_target_with_scope_cache(
10403                        node,
10404                        &reference_components,
10405                        global,
10406                        analyzer,
10407                        visibility,
10408                        ordinary_type_imports,
10409                        file,
10410                        source,
10411                        target,
10412                        true,
10413                        scope_cache,
10414                    )
10415                };
10416                match target_preserving {
10417                    LexicalTypeResolution::Resolved {
10418                        unit: _,
10419                        components,
10420                        candidates,
10421                    } if template_reference_candidates_select_target(
10422                        node,
10423                        &candidates,
10424                        analyzer,
10425                        visibility,
10426                        file,
10427                        source,
10428                        target,
10429                    ) =>
10430                    {
10431                        LexicalTypeResolution::Resolved {
10432                            unit: target.clone(),
10433                            components,
10434                            candidates,
10435                        }
10436                    }
10437                    _ => target_guided_malformed_template_alias_resolution(
10438                        node,
10439                        analyzer,
10440                        visibility,
10441                        file,
10442                        arguments,
10443                        &reference_components,
10444                        target,
10445                    )
10446                    .unwrap_or(LexicalTypeResolution::Missing),
10447                }
10448            }
10449            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
10450        };
10451    }
10452    let resolution = if let Some(recovered_scope) = recovered_scope {
10453        resolve_type_components_lexically_at_for_target_with_recovered_scope(
10454            node,
10455            &reference_components,
10456            global,
10457            analyzer,
10458            visibility,
10459            ordinary_type_imports,
10460            file,
10461            source,
10462            target,
10463            true,
10464            recovered_scope,
10465        )
10466    } else {
10467        resolve_type_components_lexically_at_for_target_with_scope_cache(
10468            node,
10469            &reference_components,
10470            global,
10471            analyzer,
10472            visibility,
10473            ordinary_type_imports,
10474            file,
10475            source,
10476            target,
10477            true,
10478            scope_cache,
10479        )
10480    };
10481    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
10482        target_guided_qualified_namespace_function_type_resolution(
10483            node,
10484            &reference_components,
10485            global,
10486            analyzer,
10487            visibility,
10488            ordinary_type_imports,
10489            file,
10490            source,
10491            target,
10492        )
10493        .unwrap_or(resolution)
10494    } else {
10495        resolution
10496    };
10497    if !is_cpp_template_argument_type_leaf(node) {
10498        return resolution;
10499    }
10500
10501    // Preprocessor recovery can lift a member declaration out of its class
10502    // field list.  The unqualified template argument is then resolved from
10503    // the namespace only, even though the indexed enclosing callable still
10504    // identifies the class owner.  Retry this exact leaf against that
10505    // structured owner scope; ordinary type nodes must continue to use the
10506    // parser-derived lexical scope so unrelated same-spelled aliases remain
10507    // excluded.
10508    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10509        return resolution;
10510    };
10511    let namespace_scope = enclosing_namespace_components(node, source);
10512    if indexed_scope.len() <= namespace_scope.len() {
10513        return resolution;
10514    }
10515    let indexed = visibility.resolve_type_components_lexically_for_target(
10516        analyzer,
10517        file,
10518        &reference_components,
10519        global,
10520        &indexed_scope,
10521        target,
10522    );
10523    match indexed {
10524        LexicalTypeResolution::Resolved {
10525            ref unit,
10526            ref candidates,
10527            ..
10528        } if (same_visible_symbol(unit, target)
10529            || candidates
10530                .iter()
10531                .any(|candidate| same_visible_symbol(candidate, target)))
10532            && visibility
10533                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10534        {
10535            indexed
10536        }
10537        _ => resolution,
10538    }
10539}
10540
10541/// Recover the lexical namespace of an out-of-line namespace function whose
10542/// relative qualifier is reachable through a `using namespace` directive.
10543///
10544/// Per-file declaration extraction can attach `void schema::consume(...)` to
10545/// the first visible namespace prefix when more than one using-directive is
10546/// active. A matching visible free-function declaration still proves the
10547/// complete namespace. Target-guided inverse lookup may use that namespace to
10548/// retry a parameter or body type, but only when the target lives in the same
10549/// namespace and the declaration's callable arity matches the definition.
10550#[allow(clippy::too_many_arguments)]
10551fn target_guided_qualified_namespace_function_type_resolution(
10552    node: Node<'_>,
10553    components: &[String],
10554    global: bool,
10555    analyzer: &CppGraphSource<'_>,
10556    visibility: &VisibilityIndex<'_>,
10557    ordinary_type_imports: &OrdinaryTypeImportCell,
10558    file: &ProjectFile,
10559    source: &str,
10560    target: &CodeUnit,
10561) -> Option<LexicalTypeResolution> {
10562    let function_definition = std::iter::successors(Some(node), |current| current.parent())
10563        .find(|current| current.kind() == "function_definition")?;
10564    let function = function_definition_name_node(function_definition)?;
10565    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
10566    let target_namespace = target.package_name();
10567    if target_namespace.is_empty() {
10568        return None;
10569    }
10570    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10571        brokk_bifrost_core::analyzer::Language::Cpp,
10572        target_namespace,
10573    );
10574    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
10575    {
10576        return None;
10577    }
10578
10579    let function_name = node_text(function_terminal_node(function), source);
10580    let definition_arity = signature_arity(Some(node_text(function_definition, source)));
10581    let declaration_proves_namespace = visibility
10582        .visible_identifier_candidates(file, function_name)
10583        .filter(|candidate| {
10584            candidate.is_function()
10585                && type_owner_of(analyzer, candidate).is_none()
10586                && candidate.package_name() == target_namespace
10587        })
10588        .any(|candidate| cpp_callable_arity(analyzer, candidate).accepts(definition_arity));
10589    if !declaration_proves_namespace {
10590        return None;
10591    }
10592
10593    let resolution = resolve_type_components_lexically_at_scoped(
10594        node,
10595        components,
10596        global,
10597        analyzer,
10598        visibility,
10599        ordinary_type_imports,
10600        file,
10601        source,
10602        Some(target),
10603        true,
10604        false,
10605        false,
10606        target_scope,
10607    );
10608    match &resolution {
10609        LexicalTypeResolution::Resolved {
10610            unit, candidates, ..
10611        } if (same_visible_symbol(unit, target)
10612            || candidates
10613                .iter()
10614                .any(|candidate| same_visible_symbol(candidate, target)))
10615            && visibility
10616                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10617        {
10618            Some(resolution)
10619        }
10620        LexicalTypeResolution::Resolved { .. }
10621        | LexicalTypeResolution::Ambiguous
10622        | LexicalTypeResolution::Missing => None,
10623    }
10624}
10625
10626#[allow(clippy::too_many_arguments)]
10627fn target_guided_malformed_template_alias_resolution(
10628    node: Node<'_>,
10629    analyzer: &CppGraphSource<'_>,
10630    visibility: &VisibilityIndex<'_>,
10631    file: &ProjectFile,
10632    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
10633    components: &[String],
10634    target: &CodeUnit,
10635) -> Option<LexicalTypeResolution> {
10636    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
10637        return None;
10638    }
10639
10640    let identifier = &components[0];
10641    let namespace =
10642        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
10643    let namespace_name = namespace.join("::");
10644    let candidates = visibility
10645        .visible_identifier_candidates(file, identifier)
10646        .filter(|candidate| {
10647            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
10648                && visibility.type_candidate_may_be_visible_before_reference(
10649                    analyzer,
10650                    file,
10651                    candidate,
10652                    node.start_byte(),
10653                )
10654        })
10655        .cloned()
10656        .collect::<Vec<_>>();
10657    let first = candidates.first()?;
10658    if !candidates
10659        .iter()
10660        .all(|candidate| same_logical_symbol(first, candidate))
10661        || !candidates.iter().all(|candidate| {
10662            visibility.template_alias_arguments_preserve_target(
10663                analyzer, file, candidate, arguments, target,
10664            )
10665        })
10666    {
10667        return None;
10668    }
10669
10670    let mut resolved_components = namespace;
10671    resolved_components.push(identifier.clone());
10672    Some(LexicalTypeResolution::Resolved {
10673        unit: target.clone(),
10674        components: resolved_components,
10675        candidates,
10676    })
10677}
10678
10679fn resolve_type_node_lexically_for_target_without_visibility(
10680    node: Node<'_>,
10681    analyzer: &CppGraphSource<'_>,
10682    visibility: &VisibilityIndex<'_>,
10683    file: &ProjectFile,
10684    source: &str,
10685    target: &CodeUnit,
10686) -> LexicalTypeResolution {
10687    let Some((components, global)) = type_reference_components(node, source) else {
10688        return LexicalTypeResolution::Missing;
10689    };
10690    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10691        node,
10692        analyzer,
10693        visibility,
10694        file,
10695        source,
10696        true,
10697        recovered_macro_decorated_declarator_type(node)
10698            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10699        &orphaned_namespace_scopes(visibility, file),
10700    ) {
10701        LexicalScopeResolution::Resolved(scope) => scope,
10702        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10703        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10704    };
10705    visibility.resolve_type_components_lexically_for_target(
10706        analyzer,
10707        file,
10708        &components,
10709        global,
10710        &lexical_scope,
10711        target,
10712    )
10713}
10714
10715fn type_node_has_exact_target_identity_without_visibility(
10716    node: Node<'_>,
10717    analyzer: &CppGraphSource<'_>,
10718    visibility: &VisibilityIndex<'_>,
10719    file: &ProjectFile,
10720    source: &str,
10721    target: &CodeUnit,
10722) -> bool {
10723    let Some((components, global)) = type_reference_components(node, source) else {
10724        return false;
10725    };
10726    let LexicalScopeResolution::Resolved(lexical_scope) =
10727        enclosing_lexical_scope_components_with_unresolved_owner(
10728            node,
10729            analyzer,
10730            visibility,
10731            file,
10732            source,
10733            true,
10734            recovered_macro_decorated_declarator_type(node)
10735                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10736            &orphaned_namespace_scopes(visibility, file),
10737        )
10738    else {
10739        return false;
10740    };
10741    let target_name = cpp_name_for(target);
10742    lexical_component_tiers(&components, global, &lexical_scope)
10743        .any(|qualified| qualified.join("::") == target_name)
10744}
10745
10746pub fn resolve_using_enum_declaration_owner(
10747    node: Node<'_>,
10748    analyzer: &CppGraphSource<'_>,
10749    visibility: &VisibilityIndex<'_>,
10750    ordinary_type_imports: &OrdinaryTypeImportCell,
10751    file: &ProjectFile,
10752    source: &str,
10753) -> LexicalTypeResolution {
10754    let Some(type_node) = using_enum_declaration_type_node(node) else {
10755        return LexicalTypeResolution::Missing;
10756    };
10757    let mut components = Vec::new();
10758    if append_cpp_name_components(type_node, source, &mut components).is_none()
10759        || components.is_empty()
10760    {
10761        return LexicalTypeResolution::Missing;
10762    }
10763    resolve_type_components_lexically_at(
10764        type_node,
10765        &components,
10766        is_globally_qualified_cpp_name(type_node),
10767        analyzer,
10768        visibility,
10769        ordinary_type_imports,
10770        file,
10771        source,
10772    )
10773}
10774
10775pub fn resolve_ordinary_using_declaration_owner(
10776    node: Node<'_>,
10777    analyzer: &CppGraphSource<'_>,
10778    visibility: &VisibilityIndex<'_>,
10779    file: &ProjectFile,
10780    source: &str,
10781) -> LexicalTypeResolution {
10782    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
10783        return LexicalTypeResolution::Missing;
10784    };
10785    let mut components = Vec::new();
10786    if append_cpp_name_components(type_node, source, &mut components).is_none()
10787        || components.len() < 2
10788    {
10789        return LexicalTypeResolution::Missing;
10790    }
10791    let lexical_scope =
10792        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
10793            LexicalScopeResolution::Resolved(scope) => scope,
10794            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10795            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10796        };
10797    visibility.resolve_type_components_lexically(
10798        analyzer,
10799        file,
10800        &components,
10801        is_globally_qualified_cpp_name(type_node),
10802        &lexical_scope,
10803    )
10804}
10805
10806pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10807    (node.kind() == "using_declaration"
10808        && (0..node.child_count()).any(|index| {
10809            node.child(index)
10810                .is_some_and(|child| child.kind() == "enum")
10811        }))
10812    .then(|| node.named_child(0))
10813    .flatten()
10814}
10815
10816pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10817    (node.kind() == "using_declaration"
10818        && using_enum_declaration_type_node(node).is_none()
10819        && using_namespace_directive_name_node(node).is_none())
10820    .then(|| node.named_child(0))
10821    .flatten()
10822}
10823
10824/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
10825/// undefined namespace-sentinel macro as a declaration whose type is the
10826/// all-caps sentinel and whose qualified declarator starts with a pseudo
10827/// `using` scope. The real imported name remains a structured qualified
10828/// identifier under that declarator. Recover only this exact CST envelope so
10829/// ordinary macro-decorated variables are not treated as imports.
10830fn recovered_macro_using_declaration_type_node<'tree>(
10831    node: Node<'tree>,
10832    source: &str,
10833) -> Option<(Node<'tree>, bool)> {
10834    if node.kind() != "declaration" {
10835        return None;
10836    }
10837    let macro_type = node.child_by_field_name("type")?;
10838    if macro_type.kind() != "type_identifier"
10839        || !cpp_export_macro_token(node_text(macro_type, source))
10840    {
10841        return None;
10842    }
10843    let declarator = node.child_by_field_name("declarator")?;
10844    if declarator.kind() != "qualified_identifier" {
10845        return None;
10846    }
10847    let scope = declarator.child_by_field_name("scope")?;
10848    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
10849        return None;
10850    }
10851    let target = declarator.child_by_field_name("name")?;
10852    let mut components = Vec::new();
10853    append_cpp_name_components(target, source, &mut components)?;
10854    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
10855}
10856
10857fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
10858    let is_directive = node.kind() == "using_directive"
10859        || (node.kind() == "using_declaration"
10860            && (0..node.child_count()).any(|index| {
10861                node.child(index)
10862                    .is_some_and(|child| child.kind() == "namespace")
10863            }));
10864    if !is_directive {
10865        return None;
10866    }
10867    node.child_by_field_name("name")
10868        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
10869}
10870
10871fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10872    let mut current = node.parent();
10873    while let Some(parent) = current {
10874        if matches!(
10875            parent.kind(),
10876            "compound_statement"
10877                | "function_definition"
10878                | "lambda_expression"
10879                | "for_statement"
10880                | "while_statement"
10881                | "if_statement"
10882                | "class_specifier"
10883                | "struct_specifier"
10884                | "union_specifier"
10885        ) {
10886            return None;
10887        }
10888        current = parent.parent();
10889    }
10890    Some(enclosing_namespace_components(node, source))
10891}
10892
10893fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
10894    let mut current = node.parent();
10895    while let Some(scope) = current {
10896        if matches!(
10897            scope.kind(),
10898            "compound_statement"
10899                | "declaration_list"
10900                | "field_declaration_list"
10901                | "translation_unit"
10902        ) {
10903            let mut depth = 0;
10904            let mut ancestor = scope.parent();
10905            while let Some(parent) = ancestor {
10906                depth += 1;
10907                ancestor = parent.parent();
10908            }
10909            return Some((
10910                scope.start_byte(),
10911                scope.end_byte(),
10912                depth,
10913                scope.kind() == "compound_statement",
10914            ));
10915        }
10916        current = scope.parent();
10917    }
10918    None
10919}
10920
10921/// Build the per-file structured using index for `file`.
10922///
10923/// The result is a pure function of the file's parsed content, which is what
10924/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
10925/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
10926/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
10927pub fn build_source_using_index(
10928    cpp: &dyn CppSource,
10929    token: QueryToken<'_>,
10930    file: &ProjectFile,
10931) -> SourceUsingIndex {
10932    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10933        return SourceUsingIndex::default();
10934    };
10935    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
10936}
10937
10938fn collect_source_using_index(
10939    cpp: &dyn CppSource,
10940    source_file: &ProjectFile,
10941    root: Node<'_>,
10942    source: &str,
10943) -> SourceUsingIndex {
10944    #[cfg(not(any(test, feature = "test-support")))]
10945    let _ = cpp;
10946    let mut index = SourceUsingIndex::default();
10947    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
10948    let mut stack = vec![root];
10949    while let Some(node) = stack.pop() {
10950        let target = match node.kind() {
10951            "using_directive" | "using_declaration" => {
10952                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
10953                    let mut namespace_components = Vec::new();
10954                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
10955                        .map(|_| EffectiveUsingTarget::Namespace {
10956                            namespace_components,
10957                            global: is_globally_qualified_cpp_name(namespace_node),
10958                        })
10959                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
10960                    let mut target_components = Vec::new();
10961                    (append_cpp_name_components(type_node, source, &mut target_components)
10962                        .is_some()
10963                        && target_components.len() >= 2)
10964                        .then(|| EffectiveUsingTarget::Ordinary {
10965                            name: target_components
10966                                .last()
10967                                .expect("ordinary using has a terminal component")
10968                                .clone(),
10969                            target_components,
10970                            global: is_globally_qualified_cpp_name(type_node),
10971                        })
10972                } else {
10973                    None
10974                }
10975            }
10976            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
10977                |(type_node, global)| {
10978                    let mut target_components = Vec::new();
10979                    (append_cpp_name_components(type_node, source, &mut target_components)
10980                        .is_some()
10981                        && target_components.len() >= 2)
10982                        .then(|| EffectiveUsingTarget::Ordinary {
10983                            name: target_components
10984                                .last()
10985                                .expect("recovered ordinary using has a terminal component")
10986                                .clone(),
10987                            target_components,
10988                            global,
10989                        })
10990                },
10991            ),
10992            _ => None,
10993        };
10994        if let Some(target) = target {
10995            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
10996            // searches from the root. The project index visits every AST node, but only these
10997            // structured using declarations need a guard environment. Keep the cheap target
10998            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
10999            // walk and the total cost is O(nodes + using declarations * ancestor depth).
11000            #[cfg(any(test, feature = "test-support"))]
11001            cpp.record_using_guard_context_inspection_for_test();
11002            let required_guards = if callable_preprocessor_context_is_visible(node, source) {
11003                Some(HashSet::default())
11004            } else {
11005                preprocessor_guard_environment(node, source)
11006            };
11007            let Some(required_guards) = required_guards else {
11008                let mut cursor = node.walk();
11009                stack.extend(node.children(&mut cursor));
11010                continue;
11011            };
11012            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11013                ordinary_using_scope(node)
11014            {
11015                let declaration_namespace = enclosing_namespace_components(node, source);
11016                let declaration_namespace = if declaration_namespace.is_empty() {
11017                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11018                        .unwrap_or(declaration_namespace)
11019                } else {
11020                    declaration_namespace
11021                };
11022                let namespace_scope = using_named_scope(node, source);
11023                let lexical_depth = declaration_namespace.len();
11024                let binding = OrdinaryTypeImport {
11025                    target,
11026                    source: source_file.clone(),
11027                    declaration_byte: node.end_byte(),
11028                    scope_start,
11029                    scope_end,
11030                    scope_depth,
11031                    block_scope,
11032                    lexical_depth,
11033                    declaration_namespace,
11034                    namespace_scope,
11035                    resolved_target_components: None,
11036                    required_guards,
11037                };
11038                match &binding.target {
11039                    EffectiveUsingTarget::Ordinary { name, .. } => index
11040                        .ordinary_by_name
11041                        .entry(name.clone())
11042                        .or_default()
11043                        .push(binding),
11044                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11045                }
11046            }
11047        }
11048        let mut cursor = node.walk();
11049        stack.extend(node.children(&mut cursor));
11050    }
11051    index
11052}
11053
11054struct OrphanedNamespaceEnvelope {
11055    body_end: usize,
11056    components: Vec<String>,
11057    class_names: HashSet<String>,
11058}
11059
11060/// Tree-sitter can terminate a namespace body at an object-like namespace
11061/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
11062/// out-of-line definitions at translation-unit scope. A block-scoped using
11063/// declaration in one of those definitions still belongs to the namespace
11064/// selected by the malformed namespace envelope. Keep the envelope scan
11065/// source-local and reuse its structural ownership evidence for each using.
11066fn collect_orphaned_namespace_envelopes(
11067    root: Node<'_>,
11068    source: &str,
11069) -> Vec<OrphanedNamespaceEnvelope> {
11070    let mut envelopes = Vec::new();
11071    let mut stack = vec![root];
11072    while let Some(current) = stack.pop() {
11073        if current.kind() == "namespace_definition"
11074            && let Some(body) = current.child_by_field_name("body")
11075            && current.end_byte() == body.end_byte()
11076            && let Some(name) = current.child_by_field_name("name")
11077        {
11078            let mut components = enclosing_namespace_components(current, source);
11079            if append_cpp_name_components(name, source, &mut components).is_some()
11080                && !components.is_empty()
11081            {
11082                let mut class_names = HashSet::default();
11083                let mut body_stack = vec![body];
11084                while let Some(node) = body_stack.pop() {
11085                    if let Some(name) = orphaned_class_definition_name(node, source) {
11086                        class_names.insert(name);
11087                    }
11088                    let mut cursor = node.walk();
11089                    if node.kind() == "ERROR" {
11090                        body_stack.extend(node.children(&mut cursor));
11091                    } else {
11092                        body_stack.extend(node.named_children(&mut cursor));
11093                    }
11094                }
11095                envelopes.push(OrphanedNamespaceEnvelope {
11096                    body_end: body.end_byte(),
11097                    components,
11098                    class_names,
11099                });
11100            }
11101        }
11102        let mut cursor = current.walk();
11103        stack.extend(current.named_children(&mut cursor));
11104    }
11105    envelopes
11106}
11107
11108fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11109    if matches!(
11110        node.kind(),
11111        "class_specifier" | "struct_specifier" | "union_specifier"
11112    ) {
11113        let body = node.child_by_field_name("body")?;
11114        let name = node.child_by_field_name("name")?;
11115        return (!name.is_missing() && !body.is_missing())
11116            .then(|| node_text(name, source).to_string());
11117    }
11118    if node.kind() != "ERROR" {
11119        return None;
11120    }
11121
11122    // When an object-like namespace macro is parsed as a function definition,
11123    // tree-sitter can place the entire class declaration inside an ERROR node
11124    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
11125    // fallback structural: accept only a named class-like keyword followed by
11126    // a real body, never an arbitrary identifier mentioned in the envelope.
11127    for index in 0..node.child_count() {
11128        let Some(keyword) = node.child(index) else {
11129            continue;
11130        };
11131        if !matches!(keyword.kind(), "class" | "struct" | "union") {
11132            continue;
11133        }
11134        let mut name = None;
11135        for next_index in (index + 1)..node.child_count() {
11136            let Some(next) = node.child(next_index) else {
11137                continue;
11138            };
11139            if next.kind() == ";" {
11140                break;
11141            }
11142            if next.kind() == "{" {
11143                return name
11144                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
11145                    .map(|name_node| node_text(name_node, source).to_string());
11146            }
11147            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11148                name = Some(next);
11149            }
11150        }
11151    }
11152    None
11153}
11154
11155fn recovered_orphaned_namespace_components(
11156    node: Node<'_>,
11157    source: &str,
11158    envelopes: &[OrphanedNamespaceEnvelope],
11159) -> Option<Vec<String>> {
11160    let owner_name = orphaned_using_owner_name(node, source)?;
11161    envelopes
11162        .iter()
11163        .filter(|envelope| {
11164            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11165        })
11166        .max_by_key(|envelope| envelope.body_end)
11167        .map(|envelope| envelope.components.clone())
11168}
11169
11170fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11171    let function = std::iter::successors(node.parent(), |current| current.parent())
11172        .find(|current| current.kind() == "function_definition")?;
11173    let owner = function_definition_owner_lookup_node(function)?;
11174    let scope = owner.child_by_field_name("scope")?;
11175    let mut components = Vec::new();
11176    append_cpp_name_components(scope, source, &mut components)?;
11177    // A qualified out-of-line member definition already carries its namespace
11178    // in the declarator scope (for example `foo::Widget::run`). The recovery
11179    // path is only for parser-orphaned top-level members whose owner scope
11180    // collapsed to the bare class name; requiring that shape prevents an
11181    // earlier, unrelated namespace/class from leaking into a global function's
11182    // using-directive lookup.
11183    if components.len() != 1 {
11184        return None;
11185    }
11186    components.pop()
11187}
11188
11189fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11190    let started = Instant::now();
11191    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11192    let source_files = visibility.all_visible_source_files();
11193    if report_stats {
11194        eprintln!(
11195            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11196            source_files.len()
11197        );
11198    }
11199    let mut project = ProjectUsingIndex::default();
11200    let mut ordinary_bindings = 0usize;
11201    for source_file in &source_files {
11202        // The per-file index is memoized on the analyzer, so assembling the
11203        // project index for a fresh `VisibilityIndex` copies bindings instead
11204        // of re-walking each file's AST (#1927).
11205        let source_index = visibility
11206            .cpp()
11207            .source_using_index(visibility.token(), source_file);
11208        for (name, bindings) in &source_index.ordinary_by_name {
11209            ordinary_bindings += bindings.len();
11210            project
11211                .ordinary_by_name
11212                .entry(name.clone())
11213                .or_default()
11214                .extend(bindings.iter().cloned());
11215        }
11216        project
11217            .directives
11218            .extend(source_index.directives.iter().cloned());
11219    }
11220    if report_stats {
11221        eprintln!(
11222            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11223            source_files.len(),
11224            project.ordinary_by_name.len(),
11225            ordinary_bindings,
11226            project.directives.len(),
11227            started.elapsed().as_millis(),
11228        );
11229    }
11230    project
11231}
11232
11233fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11234    visibility.project_using_index(|| build_project_using_index(visibility))
11235}
11236
11237/// Build the immutable project-wide using index before a parallel file scan.
11238///
11239/// Keeping the `OnceLock` publication here avoids making every scanner carry
11240/// an eager index, while callers that are about to fan out can prevent one
11241/// worker from doing the whole build as its peers wait on the lock.
11242pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11243    let _ = project_using_index(visibility);
11244}
11245
11246fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11247    let (components, global) = match &binding.target {
11248        EffectiveUsingTarget::Ordinary {
11249            target_components,
11250            global,
11251            ..
11252        } => (target_components, *global),
11253        EffectiveUsingTarget::Namespace {
11254            namespace_components,
11255            global,
11256        } => (namespace_components, *global),
11257    };
11258    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11259}
11260
11261fn using_binding_target_components_for_name(
11262    binding: &OrdinaryTypeImport,
11263    project: &ProjectUsingIndex,
11264    visibility: &VisibilityIndex<'_>,
11265    file: &ProjectFile,
11266    name: &str,
11267) -> Option<Vec<String>> {
11268    // Built once per call rather than per candidate: the filter runs over every
11269    // visible identifier of `name`, and the source is the same object each time.
11270    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
11271    let visible_candidates = visibility
11272        .visible_identifier_candidates(file, name)
11273        .filter(|candidate| {
11274            candidate.is_class()
11275                || is_type_alias(candidate)
11276                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
11277        })
11278        .collect::<Vec<_>>();
11279    if visible_candidates.is_empty() {
11280        return None;
11281    }
11282    match &binding.target {
11283        EffectiveUsingTarget::Ordinary {
11284            name: imported_name,
11285            ..
11286        } if imported_name == name => {
11287            effective_using_target_tiers(binding)
11288                .into_iter()
11289                .find(|qualified| {
11290                    let qualified_name = qualified.join("::");
11291                    visible_candidates
11292                        .iter()
11293                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
11294                })
11295        }
11296        EffectiveUsingTarget::Namespace { .. } => {
11297            visibility.note_using_namespace_lookup_for_test();
11298            let target_tiers = effective_using_target_tiers(binding);
11299            let resolved = target_tiers
11300                .iter()
11301                .find(|namespace_components| {
11302                    let namespace = namespace_components.join("::");
11303                    visible_candidates.iter().any(|candidate| {
11304                        visibility.note_using_name_candidate_inspection_for_test();
11305                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
11306                            candidate_namespace == namespace
11307                                || candidate_namespace.starts_with(&format!("{namespace}::"))
11308                        })
11309                    }) || project.directives.iter().any(|candidate| {
11310                        candidate.namespace_scope.as_deref()
11311                            == Some(namespace_components.as_slice())
11312                    }) || project
11313                        .ordinary_by_name
11314                        .values()
11315                        .flatten()
11316                        .any(|candidate| {
11317                            candidate.namespace_scope.as_deref()
11318                                == Some(namespace_components.as_slice())
11319                        })
11320                })
11321                .cloned();
11322            resolved.or_else(|| {
11323                // A sole lexical namespace tier is itself enough to retain the
11324                // directive. Candidate identity is resolved later, where
11325                // target guidance and macro-expanded owner names are available.
11326                // Dropping it here makes an unrelated same-terminal type hide
11327                // the actual namespace member before lookup can compare owners.
11328                (target_tiers.len() == 1)
11329                    .then(|| target_tiers.into_iter().next())
11330                    .flatten()
11331            })
11332        }
11333        EffectiveUsingTarget::Ordinary { .. } => None,
11334    }
11335}
11336
11337fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
11338    let start = activation.checked_sub(1)?;
11339    let mut node = root.descendant_for_byte_range(start, activation)?;
11340    while node.kind() != "preproc_include" {
11341        node = node.parent()?;
11342    }
11343    Some(node)
11344}
11345
11346fn project_using_bindings(
11347    binding: OrdinaryTypeImport,
11348    visibility: &VisibilityIndex<'_>,
11349    file: &ProjectFile,
11350    root: Node<'_>,
11351    source: &str,
11352) -> Vec<OrdinaryTypeImport> {
11353    if binding.source == *file {
11354        return vec![binding];
11355    }
11356    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
11357        return Vec::new();
11358    }
11359    visibility.note_using_donor_activation_for_test();
11360    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
11361        return Vec::new();
11362    };
11363    let projections = visibility
11364        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
11365        .map_or_else(
11366            || {
11367                visibility.conditional_include_projections_for_source(
11368                    file,
11369                    prepared.as_ref(),
11370                    &binding.source,
11371                )
11372            },
11373            |activation_byte| {
11374                Arc::from([ConditionalIncludeProjection {
11375                    activation_byte,
11376                    required_guards: HashSet::default(),
11377                    partial_guards: HashSet::default(),
11378                }])
11379            },
11380        );
11381    projections
11382        .iter()
11383        .cloned()
11384        .filter_map(|projection| {
11385            let required_guards =
11386                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
11387            let mut projected = binding.clone();
11388            projected.required_guards = required_guards;
11389            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
11390        })
11391        .collect()
11392}
11393
11394fn project_using_binding_at_activation(
11395    mut binding: OrdinaryTypeImport,
11396    activation: usize,
11397    root: Node<'_>,
11398    source: &str,
11399) -> Option<OrdinaryTypeImport> {
11400    let include = include_node_for_activation(root, activation)?;
11401    let include_namespace = enclosing_namespace_components(include, source);
11402    let mut declaration_namespace = include_namespace.clone();
11403    declaration_namespace.extend(binding.declaration_namespace);
11404    binding.declaration_namespace = declaration_namespace;
11405    binding.declaration_byte = activation;
11406    if let Some(prefix) = using_named_scope(include, source) {
11407        let mut projected = prefix;
11408        projected.extend(binding.namespace_scope.take().unwrap_or_default());
11409        binding.scope_depth = projected.len();
11410        binding.block_scope = false;
11411        binding.lexical_depth = projected.len();
11412        binding.namespace_scope = Some(projected);
11413        binding.scope_start = 0;
11414        binding.scope_end = usize::MAX;
11415        Some(binding)
11416    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
11417        binding.namespace_scope = None;
11418        binding.scope_start = start;
11419        binding.scope_end = end;
11420        binding.scope_depth = depth;
11421        binding.block_scope = block_scope;
11422        binding.lexical_depth = include_namespace.len();
11423        Some(binding)
11424    } else {
11425        None
11426    }
11427}
11428
11429/// `node` may be any node of `file`'s tree; the projection reaches the tree
11430/// root itself when it needs one. `Node::parent` re-descends from the root on
11431/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
11432/// call site cost a near-full-AST scan per reference on a large flat file
11433/// (#1927); a name with no candidate bindings never pays for it.
11434pub fn effective_using_bindings_for_name(
11435    visibility: &VisibilityIndex<'_>,
11436    imports: &OrdinaryTypeImportCell,
11437    file: &ProjectFile,
11438    node: Node<'_>,
11439    source: &str,
11440    name: &str,
11441) -> Arc<[OrdinaryTypeImport]> {
11442    imports
11443        .projection_cell(name)
11444        .get_or_init(|| {
11445            let project = project_using_index(visibility);
11446            let name_bindings = project.ordinary_by_name.get(name);
11447            if name_bindings.is_none() && project.directives.is_empty() {
11448                return Arc::from(Vec::new());
11449            }
11450            let root = root_node(node);
11451            let mut projected = Vec::new();
11452            for binding in name_bindings
11453                .into_iter()
11454                .flatten()
11455                .chain(project.directives.iter())
11456            {
11457                if !visibility.source_is_visible(file, &binding.source) {
11458                    continue;
11459                }
11460                let target_components = using_binding_target_components_for_name(
11461                    binding, project, visibility, file, name,
11462                )
11463                .or_else(|| match &binding.target {
11464                    EffectiveUsingTarget::Ordinary {
11465                        name: imported_name,
11466                        target_components,
11467                        ..
11468                    } if imported_name == name => Some(target_components.clone()),
11469                    EffectiveUsingTarget::Ordinary { .. }
11470                    | EffectiveUsingTarget::Namespace { .. } => None,
11471                });
11472                let Some(target_components) = target_components else {
11473                    continue;
11474                };
11475                let mut binding = binding.clone();
11476                binding.resolved_target_components = Some(target_components);
11477                projected.extend(project_using_bindings(
11478                    binding, visibility, file, root, source,
11479                ));
11480            }
11481            Arc::from(projected)
11482        })
11483        .clone()
11484}
11485
11486pub fn initialized_ordinary_type_imports(
11487    root: Node<'_>,
11488    analyzer: &CppGraphSource<'_>,
11489    visibility: &VisibilityIndex<'_>,
11490    file: &ProjectFile,
11491    source: &str,
11492) -> OrdinaryTypeImportCell {
11493    let cell = visibility.ordinary_type_import_cell(file);
11494    let _ = (root, analyzer, source);
11495    cell
11496}
11497
11498fn root_node(mut node: Node<'_>) -> Node<'_> {
11499    while let Some(parent) = node.parent() {
11500        node = parent;
11501    }
11502    node
11503}
11504
11505/// `reference_guards` is the reference node's guard environment, computed once
11506/// by the caller and shared across every binding: recomputing it per binding
11507/// repeated a full ancestor climb whose every `Node::parent` step re-descends
11508/// from the root (#1927).
11509fn effective_using_binding_active(
11510    binding: &OrdinaryTypeImport,
11511    node: Node<'_>,
11512    lexical_scope: &[String],
11513    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11514    visibility: &VisibilityIndex<'_>,
11515    file: &ProjectFile,
11516) -> bool {
11517    effective_using_binding_guards_active(
11518        binding,
11519        node.start_byte(),
11520        reference_guards,
11521        visibility,
11522        file,
11523    ) && binding.namespace_scope.as_ref().map_or_else(
11524        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11525        |namespace| lexical_scope.starts_with(namespace),
11526    )
11527}
11528
11529fn effective_using_binding_guards_active(
11530    binding: &OrdinaryTypeImport,
11531    reference_byte: usize,
11532    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11533    visibility: &VisibilityIndex<'_>,
11534    file: &ProjectFile,
11535) -> bool {
11536    binding.declaration_byte <= reference_byte
11537        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
11538        && visibility.preprocessor_guards_stable_between(
11539            file,
11540            binding.declaration_byte,
11541            reference_byte,
11542            &binding.required_guards,
11543        )
11544}
11545
11546fn effective_using_binding_guards_compatible(
11547    binding: &OrdinaryTypeImport,
11548    reference_byte: usize,
11549    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11550    visibility: &VisibilityIndex<'_>,
11551    file: &ProjectFile,
11552) -> bool {
11553    binding.source != *file
11554        && !binding.required_guards.is_empty()
11555        && binding.declaration_byte <= reference_byte
11556        && reference_guards.is_some_and(|active| {
11557            !binding.required_guards.is_subset(active)
11558                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
11559        })
11560        && visibility.preprocessor_guards_stable_between(
11561            file,
11562            binding.declaration_byte,
11563            reference_byte,
11564            &binding.required_guards,
11565        )
11566}
11567
11568#[allow(clippy::too_many_arguments)]
11569fn binding_type_candidates(
11570    binding: &OrdinaryTypeImport,
11571    active_bindings: &[&OrdinaryTypeImport],
11572    analyzer: &CppGraphSource<'_>,
11573    visibility: &VisibilityIndex<'_>,
11574    file: &ProjectFile,
11575    name: &str,
11576    direct_target: Option<&CodeUnit>,
11577    reference_byte: usize,
11578) -> Vec<(CodeUnit, Vec<String>)> {
11579    let Some(qualified) = binding.resolved_target_components.clone() else {
11580        return Vec::new();
11581    };
11582    let mut targets = Vec::new();
11583    match binding.target {
11584        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
11585        EffectiveUsingTarget::Namespace { .. } => {
11586            let mut stack = vec![qualified];
11587            let mut visited = HashSet::default();
11588            while let Some(namespace) = stack.pop() {
11589                if !visited.insert(namespace.clone()) {
11590                    continue;
11591                }
11592                let mut target = namespace.clone();
11593                target.push(name.to_string());
11594                targets.push(target);
11595                stack.extend(active_bindings.iter().filter_map(|candidate| {
11596                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
11597                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
11598                    .then(|| candidate.resolved_target_components.clone())
11599                    .flatten()
11600                }));
11601            }
11602        }
11603    }
11604    targets
11605        .into_iter()
11606        .flat_map(|target| {
11607            let mut candidates = visibility
11608                .visible_identifier_candidates(file, name)
11609                .filter(|candidate| {
11610                    (candidate.is_class() || is_type_alias(candidate))
11611                        && type_candidate_matches_lookup_components(
11612                            analyzer,
11613                            visibility,
11614                            file,
11615                            candidate,
11616                            reference_byte,
11617                            &target,
11618                        )
11619                })
11620                .cloned()
11621                .collect::<Vec<_>>();
11622            if candidates.is_empty()
11623                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11624                && let Some(target_unit) = direct_target
11625            {
11626                let expanded_target_name = macro_expanded_cpp_name_components(
11627                    visibility,
11628                    file,
11629                    target_unit,
11630                    reference_byte,
11631                );
11632                if (target_unit.is_class() || is_type_alias(target_unit))
11633                    && expanded_target_name == target
11634                    && visibility.external_type_candidate_visible_at(
11635                        file,
11636                        target_unit,
11637                        reference_byte,
11638                    )
11639                {
11640                    candidates.push(target_unit.clone());
11641                }
11642            }
11643            if candidates.is_empty()
11644                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11645                && let Some(target_unit) = direct_target
11646            {
11647                let visible_types = visibility
11648                    .visible_identifier_candidates(file, name)
11649                    .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
11650                    .collect::<Vec<_>>();
11651                let uniquely_names_target = !visible_types.is_empty()
11652                    && visible_types
11653                        .iter()
11654                        .all(|candidate| same_visible_symbol(candidate, target_unit));
11655                if uniquely_names_target {
11656                    candidates.extend(visible_types.into_iter().cloned());
11657                }
11658            }
11659            candidates
11660                .into_iter()
11661                .map(move |candidate| (candidate, target.clone()))
11662        })
11663        .collect()
11664}
11665
11666fn macro_expanded_cpp_name_components(
11667    visibility: &VisibilityIndex<'_>,
11668    file: &ProjectFile,
11669    unit: &CodeUnit,
11670    reference_byte: usize,
11671) -> Vec<String> {
11672    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11673        brokk_bifrost_core::analyzer::Language::Cpp,
11674        &cpp_name_for(unit),
11675    )
11676    .into_iter()
11677    .flat_map(|component| {
11678        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11679    })
11680    .collect()
11681}
11682
11683fn macro_expanded_cpp_name_component(
11684    visibility: &VisibilityIndex<'_>,
11685    file: &ProjectFile,
11686    component: String,
11687    reference_byte: usize,
11688) -> Vec<String> {
11689    let Some(replacement) =
11690        visibility.object_macro_replacement_at(file, &component, reference_byte)
11691    else {
11692        return vec![component];
11693    };
11694    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11695        brokk_bifrost_core::analyzer::Language::Cpp,
11696        &replacement,
11697    );
11698    if expanded.is_empty() {
11699        vec![component]
11700    } else {
11701        expanded
11702    }
11703}
11704
11705/// Whether a visible type has the requested qualified spelling.
11706///
11707/// Members of an inline namespace are also members of its enclosing namespace,
11708/// so an ordinary using-declaration may legally omit the inline component. The
11709/// stored FQ name retains that component to keep declarations distinct. Recover
11710/// the omitted spellings from the candidate declaration's namespace ancestors,
11711/// using CST `inline` tokens rather than guessing from names.
11712fn type_candidate_matches_lookup_components(
11713    analyzer: &CppGraphSource<'_>,
11714    visibility: &VisibilityIndex<'_>,
11715    file: &ProjectFile,
11716    candidate: &CodeUnit,
11717    reference_byte: usize,
11718    target: &[String],
11719) -> bool {
11720    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11721    if expanded == target {
11722        return true;
11723    }
11724    let Some(cpp) = analyzer.cpp else {
11725        return false;
11726    };
11727    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11728        return false;
11729    };
11730    let root = prepared.tree().root_node();
11731    for range in analyzer.ranges(candidate) {
11732        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11733        else {
11734            continue;
11735        };
11736        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11737        loop {
11738            if current.kind() == "namespace_definition"
11739                && let Some(name) = current.child_by_field_name("name")
11740            {
11741                let mut components = Vec::new();
11742                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11743                    && !components.is_empty()
11744                {
11745                    let inline = (0..current.child_count())
11746                        .filter_map(|index| current.child(index))
11747                        .any(|child| !child.is_named() && child.kind() == "inline");
11748                    namespaces.push((components, inline));
11749                }
11750            }
11751            let Some(parent) = current.parent() else {
11752                break;
11753            };
11754            current = parent;
11755        }
11756        namespaces.reverse();
11757        let mut namespace_components = Vec::new();
11758        let mut inline_indexes = HashSet::default();
11759        for (components, inline) in namespaces {
11760            for component in components {
11761                let expanded_component =
11762                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11763                if inline {
11764                    inline_indexes.extend(
11765                        namespace_components.len()
11766                            ..namespace_components.len() + expanded_component.len(),
11767                    );
11768                }
11769                namespace_components.extend(expanded_component);
11770            }
11771        }
11772        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11773            continue;
11774        }
11775        // Each inline namespace component can be present or elided. Compare
11776        // those alternatives as a small dynamic program instead of generating
11777        // every subset of a deeply nested inline-namespace chain.
11778        let mut reachable = vec![false; target.len() + 1];
11779        reachable[0] = true;
11780        for (index, component) in expanded.iter().enumerate() {
11781            let mut next = vec![false; target.len() + 1];
11782            for (target_index, reached) in reachable.iter().copied().enumerate() {
11783                if !reached {
11784                    continue;
11785                }
11786                if inline_indexes.contains(&index) {
11787                    next[target_index] = true;
11788                }
11789                if target
11790                    .get(target_index)
11791                    .is_some_and(|target_component| target_component == component)
11792                {
11793                    next[target_index + 1] = true;
11794                }
11795            }
11796            reachable = next;
11797        }
11798        if reachable[target.len()] {
11799            return true;
11800        }
11801    }
11802    false
11803}
11804
11805#[allow(clippy::too_many_arguments)]
11806fn resolved_type_import(
11807    candidates: Vec<(CodeUnit, Vec<String>)>,
11808    lexical_depth: usize,
11809    is_direct: bool,
11810    analyzer: &CppGraphSource<'_>,
11811    visibility: &VisibilityIndex<'_>,
11812    file: &ProjectFile,
11813    direct_target: Option<&CodeUnit>,
11814) -> OrdinaryTypeImportResolution {
11815    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
11816    for candidate in candidates {
11817        if !logical
11818            .iter()
11819            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
11820        {
11821            logical.push(candidate);
11822        }
11823    }
11824    let selected = match logical.as_slice() {
11825        [] => return OrdinaryTypeImportResolution::Missing,
11826        [only] => only,
11827        // Several declarations of one FQN in one file are configuration
11828        // spellings of one entity, not competing types (#1845): the imported
11829        // name is unambiguous, only the branch that supplies it depends on the
11830        // build.
11831        several => {
11832            let units = several
11833                .iter()
11834                .map(|(unit, _)| unit)
11835                .collect::<Vec<&CodeUnit>>();
11836            let Some(spelling) = direct_target.and_then(|target| {
11837                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
11838            }) else {
11839                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
11840            };
11841            several
11842                .iter()
11843                .find(|(unit, _)| same_symbol(unit, spelling))
11844                .expect("the selected spelling is one of the imported candidates")
11845        }
11846    };
11847    OrdinaryTypeImportResolution::Resolved {
11848        target: selected.0.clone(),
11849        target_components: selected.1.clone(),
11850        lexical_depth,
11851        is_direct,
11852    }
11853}
11854
11855#[allow(clippy::too_many_arguments)]
11856fn ordinary_type_import_resolution(
11857    node: Node<'_>,
11858    components: &[String],
11859    global: bool,
11860    analyzer: &CppGraphSource<'_>,
11861    visibility: &VisibilityIndex<'_>,
11862    imports: &OrdinaryTypeImportCell,
11863    file: &ProjectFile,
11864    source: &str,
11865    lexical_scope: &[String],
11866    direct_target: Option<&CodeUnit>,
11867) -> OrdinaryTypeImportResolution {
11868    if global || components.len() != 1 {
11869        return OrdinaryTypeImportResolution::Missing;
11870    }
11871    let name = &components[0];
11872    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11873    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
11874    // step re-descends from the root (#1927). A name with no bindings needs
11875    // none of it, and one environment serves every binding of the reference.
11876    if bindings.is_empty() {
11877        return OrdinaryTypeImportResolution::Missing;
11878    }
11879    let reference_guards = preprocessor_guard_environment(node, source);
11880    let active = bindings
11881        .iter()
11882        .filter(|binding| {
11883            effective_using_binding_active(
11884                binding,
11885                node,
11886                lexical_scope,
11887                reference_guards.as_ref(),
11888                visibility,
11889                file,
11890            )
11891        })
11892        .collect::<Vec<_>>();
11893    let transitive = bindings
11894        .iter()
11895        .filter(|binding| {
11896            effective_using_binding_guards_active(
11897                binding,
11898                node.start_byte(),
11899                reference_guards.as_ref(),
11900                visibility,
11901                file,
11902            ) && (binding.namespace_scope.is_some()
11903                || (binding.scope_start <= node.start_byte()
11904                    && node.end_byte() <= binding.scope_end))
11905        })
11906        .collect::<Vec<_>>();
11907    ordinary_type_import_resolution_for_bindings(
11908        node,
11909        name,
11910        analyzer,
11911        visibility,
11912        file,
11913        lexical_scope,
11914        direct_target,
11915        &active,
11916        &transitive,
11917    )
11918}
11919
11920#[allow(clippy::too_many_arguments)]
11921fn compatible_foreign_type_import_resolution(
11922    node: Node<'_>,
11923    components: &[String],
11924    global: bool,
11925    analyzer: &CppGraphSource<'_>,
11926    visibility: &VisibilityIndex<'_>,
11927    imports: &OrdinaryTypeImportCell,
11928    file: &ProjectFile,
11929    source: &str,
11930    lexical_scope: &[String],
11931    direct_target: Option<&CodeUnit>,
11932) -> OrdinaryTypeImportResolution {
11933    if global || components.len() != 1 {
11934        return OrdinaryTypeImportResolution::Missing;
11935    }
11936    let name = &components[0];
11937    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11938    if bindings.is_empty() {
11939        return OrdinaryTypeImportResolution::Missing;
11940    }
11941    let reference_guards = preprocessor_guard_environment(node, source);
11942    let compatible = bindings
11943        .iter()
11944        .filter(|binding| {
11945            effective_using_binding_guards_compatible(
11946                binding,
11947                node.start_byte(),
11948                reference_guards.as_ref(),
11949                visibility,
11950                file,
11951            ) && binding.namespace_scope.as_ref().map_or_else(
11952                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11953                |namespace| lexical_scope.starts_with(namespace),
11954            )
11955        })
11956        .collect::<Vec<_>>();
11957    let transitive = bindings
11958        .iter()
11959        .filter(|binding| {
11960            effective_using_binding_guards_compatible(
11961                binding,
11962                node.start_byte(),
11963                reference_guards.as_ref(),
11964                visibility,
11965                file,
11966            ) && (binding.namespace_scope.is_some()
11967                || (binding.scope_start <= node.start_byte()
11968                    && node.end_byte() <= binding.scope_end))
11969        })
11970        .collect::<Vec<_>>();
11971    ordinary_type_import_resolution_for_bindings(
11972        node,
11973        name,
11974        analyzer,
11975        visibility,
11976        file,
11977        lexical_scope,
11978        direct_target,
11979        &compatible,
11980        &transitive,
11981    )
11982}
11983
11984#[allow(clippy::too_many_arguments)]
11985fn ordinary_type_import_resolution_for_bindings(
11986    node: Node<'_>,
11987    name: &str,
11988    analyzer: &CppGraphSource<'_>,
11989    visibility: &VisibilityIndex<'_>,
11990    file: &ProjectFile,
11991    lexical_scope: &[String],
11992    direct_target: Option<&CodeUnit>,
11993    active: &[&OrdinaryTypeImport],
11994    transitive: &[&OrdinaryTypeImport],
11995) -> OrdinaryTypeImportResolution {
11996    let mut concrete_depths = active
11997        .iter()
11998        .filter(|binding| binding.namespace_scope.is_none())
11999        .map(|binding| binding.scope_depth)
12000        .collect::<Vec<_>>();
12001    concrete_depths.sort_unstable();
12002    concrete_depths.dedup();
12003    for depth in concrete_depths.into_iter().rev() {
12004        let at_tier = active
12005            .iter()
12006            .copied()
12007            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12008        let direct = at_tier
12009            .clone()
12010            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12011            .flat_map(|binding| {
12012                binding_type_candidates(
12013                    binding,
12014                    transitive,
12015                    analyzer,
12016                    visibility,
12017                    file,
12018                    name,
12019                    direct_target,
12020                    node.start_byte(),
12021                )
12022            })
12023            .collect::<Vec<_>>();
12024        if !direct.is_empty() {
12025            return resolved_type_import(
12026                direct,
12027                lexical_scope.len(),
12028                true,
12029                analyzer,
12030                visibility,
12031                file,
12032                direct_target,
12033            );
12034        }
12035        let directives = at_tier
12036            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12037            .flat_map(|binding| {
12038                binding_type_candidates(
12039                    binding,
12040                    transitive,
12041                    analyzer,
12042                    visibility,
12043                    file,
12044                    name,
12045                    direct_target,
12046                    node.start_byte(),
12047                )
12048            })
12049            .collect::<Vec<_>>();
12050        if !directives.is_empty() {
12051            return resolved_type_import(
12052                directives,
12053                lexical_scope.len(),
12054                false,
12055                analyzer,
12056                visibility,
12057                file,
12058                direct_target,
12059            );
12060        }
12061    }
12062    for prefix_len in (0..=lexical_scope.len()).rev() {
12063        let tier = &lexical_scope[..prefix_len];
12064        let at_tier = active
12065            .iter()
12066            .copied()
12067            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12068        let direct = at_tier
12069            .clone()
12070            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12071            .flat_map(|binding| {
12072                binding_type_candidates(
12073                    binding,
12074                    transitive,
12075                    analyzer,
12076                    visibility,
12077                    file,
12078                    name,
12079                    direct_target,
12080                    node.start_byte(),
12081                )
12082            })
12083            .collect::<Vec<_>>();
12084        if !direct.is_empty() {
12085            return resolved_type_import(
12086                direct,
12087                prefix_len,
12088                true,
12089                analyzer,
12090                visibility,
12091                file,
12092                direct_target,
12093            );
12094        }
12095        let directives = at_tier
12096            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12097            .flat_map(|binding| {
12098                binding_type_candidates(
12099                    binding,
12100                    transitive,
12101                    analyzer,
12102                    visibility,
12103                    file,
12104                    name,
12105                    direct_target,
12106                    node.start_byte(),
12107                )
12108            })
12109            .collect::<Vec<_>>();
12110        if !directives.is_empty() {
12111            return resolved_type_import(
12112                directives,
12113                prefix_len,
12114                false,
12115                analyzer,
12116                visibility,
12117                file,
12118                direct_target,
12119            );
12120        }
12121    }
12122    OrdinaryTypeImportResolution::Missing
12123}
12124
12125#[allow(clippy::too_many_arguments)]
12126pub fn resolve_type_components_lexically_at(
12127    node: Node<'_>,
12128    components: &[String],
12129    global: bool,
12130    analyzer: &CppGraphSource<'_>,
12131    visibility: &VisibilityIndex<'_>,
12132    ordinary_type_imports: &OrdinaryTypeImportCell,
12133    file: &ProjectFile,
12134    source: &str,
12135) -> LexicalTypeResolution {
12136    resolve_type_components_lexically_at_inner(
12137        node,
12138        components,
12139        global,
12140        analyzer,
12141        visibility,
12142        ordinary_type_imports,
12143        file,
12144        source,
12145        None,
12146        false,
12147        false,
12148        false,
12149        None,
12150    )
12151}
12152
12153/// Resolve a type at its lexical reference site while retaining the identity
12154/// of an alias that C++ lookup selects.
12155///
12156/// Forward navigation uses the selected spelling as its destination, whereas
12157/// graph attribution normally canonicalizes an alias to its target. Both
12158/// surfaces must still apply the same ordinary using-declarations, declaration
12159/// order, guard state, and lexical-depth precedence.
12160#[allow(clippy::too_many_arguments)]
12161pub fn resolve_type_components_lexically_at_preserving_alias(
12162    node: Node<'_>,
12163    components: &[String],
12164    global: bool,
12165    analyzer: &CppGraphSource<'_>,
12166    visibility: &VisibilityIndex<'_>,
12167    file: &ProjectFile,
12168    source: &str,
12169) -> LexicalTypeResolution {
12170    let ordinary_type_imports =
12171        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12172    resolve_type_components_lexically_at_inner(
12173        node,
12174        components,
12175        global,
12176        analyzer,
12177        visibility,
12178        &ordinary_type_imports,
12179        file,
12180        source,
12181        None,
12182        false,
12183        true,
12184        true,
12185        None,
12186    )
12187}
12188
12189#[allow(clippy::too_many_arguments)]
12190fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12191    node: Node<'_>,
12192    components: &[String],
12193    global: bool,
12194    analyzer: &CppGraphSource<'_>,
12195    visibility: &VisibilityIndex<'_>,
12196    ordinary_type_imports: &OrdinaryTypeImportCell,
12197    file: &ProjectFile,
12198    source: &str,
12199    scope_cache: Option<&LexicalScopeCache>,
12200) -> LexicalTypeResolution {
12201    resolve_type_components_lexically_at_inner(
12202        node,
12203        components,
12204        global,
12205        analyzer,
12206        visibility,
12207        ordinary_type_imports,
12208        file,
12209        source,
12210        None,
12211        false,
12212        true,
12213        false,
12214        scope_cache,
12215    )
12216}
12217
12218#[allow(clippy::too_many_arguments)]
12219fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12220    node: Node<'_>,
12221    components: &[String],
12222    global: bool,
12223    analyzer: &CppGraphSource<'_>,
12224    visibility: &VisibilityIndex<'_>,
12225    ordinary_type_imports: &OrdinaryTypeImportCell,
12226    file: &ProjectFile,
12227    source: &str,
12228    target: &CodeUnit,
12229    apply_structured_prefilter: bool,
12230    scope_cache: Option<&LexicalScopeCache>,
12231) -> LexicalTypeResolution {
12232    resolve_type_components_lexically_at_inner(
12233        node,
12234        components,
12235        global,
12236        analyzer,
12237        visibility,
12238        ordinary_type_imports,
12239        file,
12240        source,
12241        Some(target),
12242        apply_structured_prefilter,
12243        false,
12244        false,
12245        scope_cache,
12246    )
12247}
12248
12249#[allow(clippy::too_many_arguments)]
12250fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12251    node: Node<'_>,
12252    components: &[String],
12253    global: bool,
12254    analyzer: &CppGraphSource<'_>,
12255    visibility: &VisibilityIndex<'_>,
12256    ordinary_type_imports: &OrdinaryTypeImportCell,
12257    file: &ProjectFile,
12258    source: &str,
12259    recovered_scope: &[String],
12260) -> LexicalTypeResolution {
12261    resolve_type_components_in_authoritative_scope(
12262        node,
12263        components,
12264        global,
12265        analyzer,
12266        visibility,
12267        ordinary_type_imports,
12268        file,
12269        source,
12270        None,
12271        false,
12272        true,
12273        false,
12274        recovered_scope.to_vec(),
12275    )
12276}
12277
12278#[allow(clippy::too_many_arguments)]
12279fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12280    node: Node<'_>,
12281    components: &[String],
12282    global: bool,
12283    analyzer: &CppGraphSource<'_>,
12284    visibility: &VisibilityIndex<'_>,
12285    ordinary_type_imports: &OrdinaryTypeImportCell,
12286    file: &ProjectFile,
12287    source: &str,
12288    target: &CodeUnit,
12289    apply_structured_prefilter: bool,
12290    recovered_scope: &[String],
12291) -> LexicalTypeResolution {
12292    resolve_type_components_in_authoritative_scope(
12293        node,
12294        components,
12295        global,
12296        analyzer,
12297        visibility,
12298        ordinary_type_imports,
12299        file,
12300        source,
12301        Some(target),
12302        apply_structured_prefilter,
12303        false,
12304        false,
12305        recovered_scope.to_vec(),
12306    )
12307}
12308
12309#[allow(clippy::too_many_arguments)]
12310fn resolve_type_components_lexically_at_inner(
12311    node: Node<'_>,
12312    components: &[String],
12313    global: bool,
12314    analyzer: &CppGraphSource<'_>,
12315    visibility: &VisibilityIndex<'_>,
12316    ordinary_type_imports: &OrdinaryTypeImportCell,
12317    file: &ProjectFile,
12318    source: &str,
12319    direct_target: Option<&CodeUnit>,
12320    apply_structured_prefilter: bool,
12321    preserve_alias: bool,
12322    allow_compatible_foreign_import: bool,
12323    scope_cache: Option<&LexicalScopeCache>,
12324) -> LexicalTypeResolution {
12325    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12326    let lexical_scope_started = Instant::now();
12327    if report_stats {
12328        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
12329    }
12330    let lexical_scope = if global {
12331        Vec::new()
12332    } else {
12333        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
12334            node,
12335            analyzer,
12336            visibility,
12337            file,
12338            source,
12339            true,
12340            recovered_macro_decorated_declarator_type(node)
12341                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
12342            scope_cache,
12343        ) {
12344            LexicalScopeResolution::Resolved(scope) => scope,
12345            LexicalScopeResolution::Ambiguous => {
12346                if report_stats {
12347                    eprintln!(
12348                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
12349                        lexical_scope_started.elapsed().as_millis(),
12350                    );
12351                }
12352                return LexicalTypeResolution::Ambiguous;
12353            }
12354            LexicalScopeResolution::Missing => {
12355                if report_stats {
12356                    eprintln!(
12357                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
12358                        lexical_scope_started.elapsed().as_millis(),
12359                    );
12360                }
12361                return LexicalTypeResolution::Missing;
12362            }
12363        }
12364    };
12365    if report_stats {
12366        eprintln!(
12367            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
12368            lexical_scope.len(),
12369            lexical_scope_started.elapsed().as_millis(),
12370        );
12371    }
12372    resolve_type_components_lexically_at_scoped(
12373        node,
12374        components,
12375        global,
12376        analyzer,
12377        visibility,
12378        ordinary_type_imports,
12379        file,
12380        source,
12381        direct_target,
12382        apply_structured_prefilter,
12383        preserve_alias,
12384        allow_compatible_foreign_import,
12385        lexical_scope,
12386    )
12387}
12388
12389#[allow(clippy::too_many_arguments)]
12390fn resolve_type_components_lexically_at_scoped(
12391    node: Node<'_>,
12392    components: &[String],
12393    global: bool,
12394    analyzer: &CppGraphSource<'_>,
12395    visibility: &VisibilityIndex<'_>,
12396    ordinary_type_imports: &OrdinaryTypeImportCell,
12397    file: &ProjectFile,
12398    source: &str,
12399    direct_target: Option<&CodeUnit>,
12400    apply_structured_prefilter: bool,
12401    preserve_alias: bool,
12402    allow_compatible_foreign_import: bool,
12403    mut lexical_scope: Vec<String>,
12404) -> LexicalTypeResolution {
12405    if !global
12406        && components.len() == 1
12407        // The two constant-time conditions run before the ancestor climb: each
12408        // `Node::parent` step re-descends from the root (#1927).
12409        && let Some(target) = direct_target
12410        && lexical_scope
12411            .last()
12412            .is_none_or(|last| last != &components[0])
12413        // A recovered class may contain a real member function nested inside
12414        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
12415        // XMLConstHandle). The nearest function_definition is then the member
12416        // itself, so inspect the complete ancestor chain.
12417        && has_malformed_wrapper_function_definition_ancestor(node)
12418        && let Some(indexed_namespace) =
12419            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
12420        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
12421    {
12422        lexical_scope = indexed_namespace;
12423    }
12424    resolve_type_components_in_authoritative_scope(
12425        node,
12426        components,
12427        global,
12428        analyzer,
12429        visibility,
12430        ordinary_type_imports,
12431        file,
12432        source,
12433        direct_target,
12434        apply_structured_prefilter,
12435        preserve_alias,
12436        allow_compatible_foreign_import,
12437        lexical_scope,
12438    )
12439}
12440
12441/// Resolve within a scope already proven by recovered syntax.
12442///
12443/// Unlike parser-derived scope, this scope must not be replaced with the
12444/// queried target's namespace: doing so would let target guidance override a
12445/// nearer declaration represented by the recovered syntax.
12446#[allow(clippy::too_many_arguments)]
12447fn resolve_type_components_in_authoritative_scope(
12448    node: Node<'_>,
12449    components: &[String],
12450    global: bool,
12451    analyzer: &CppGraphSource<'_>,
12452    visibility: &VisibilityIndex<'_>,
12453    ordinary_type_imports: &OrdinaryTypeImportCell,
12454    file: &ProjectFile,
12455    source: &str,
12456    direct_target: Option<&CodeUnit>,
12457    apply_structured_prefilter: bool,
12458    preserve_alias: bool,
12459    allow_compatible_foreign_import: bool,
12460    lexical_scope: Vec<String>,
12461) -> LexicalTypeResolution {
12462    if apply_structured_prefilter
12463        && direct_target.is_some()
12464        && !preserve_alias
12465        && !global
12466        && components.len() == 1
12467        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
12468    {
12469        return LexicalTypeResolution::Missing;
12470    }
12471    // A recovered macro-prefixed return type can share its global spelling
12472    // with aliases from mutually exclusive included headers. C++ lookup uses
12473    // the declaration physically present earlier in this file; the visibility
12474    // index deliberately retains every configuration alternative. Restore
12475    // that precedence only for the exact recovered scope and lexical tier.
12476    if !global
12477        && components.len() == 1
12478        && recovered_macro_decorated_type_node(node).is_some()
12479        && let Some(resolution) = recovered_same_file_type_alias_resolution(
12480            node,
12481            components,
12482            analyzer,
12483            visibility,
12484            file,
12485            direct_target,
12486            &lexical_scope,
12487        )
12488    {
12489        return resolution;
12490    }
12491    if apply_structured_prefilter
12492        && let Some(target) = direct_target
12493        && !preserve_alias
12494        && !visibility.structured_type_reference_may_resolve_to_target(
12495            analyzer,
12496            file,
12497            components,
12498            global,
12499            &lexical_scope,
12500            target,
12501        )
12502    {
12503        return LexicalTypeResolution::Missing;
12504    }
12505    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12506    let lexical_normal_started = Instant::now();
12507    if report_stats {
12508        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
12509    }
12510    let normal = if preserve_alias {
12511        visibility.resolve_type_components_lexically_for_forward(
12512            analyzer,
12513            file,
12514            components,
12515            global,
12516            &lexical_scope,
12517        )
12518    } else {
12519        direct_target.map_or_else(
12520            || {
12521                visibility.resolve_type_components_lexically(
12522                    analyzer,
12523                    file,
12524                    components,
12525                    global,
12526                    &lexical_scope,
12527                )
12528            },
12529            |target| {
12530                visibility.resolve_type_components_lexically_for_target(
12531                    analyzer,
12532                    file,
12533                    components,
12534                    global,
12535                    &lexical_scope,
12536                    target,
12537                )
12538            },
12539        )
12540    };
12541    if report_stats {
12542        eprintln!(
12543            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
12544            lexical_normal_started.elapsed().as_millis(),
12545        );
12546    }
12547    let normal = match normal {
12548        LexicalTypeResolution::Resolved { ref unit, .. }
12549            if !visibility
12550                .external_type_candidate_visible_in_context(analyzer, file, unit, node)
12551                && !direct_target.is_some_and(|target| {
12552                    let candidate_refs = visibility
12553                        .visible_identifier_candidates(file, target.identifier())
12554                        .collect::<Vec<_>>();
12555                    visibility.c_tag_declaration_family_matches_target(
12556                        analyzer,
12557                        file,
12558                        &candidate_refs,
12559                        target,
12560                    )
12561                }) =>
12562        {
12563            LexicalTypeResolution::Missing
12564        }
12565        resolution => resolution,
12566    };
12567    let normal_depth = match &normal {
12568        LexicalTypeResolution::Resolved { components, .. } => {
12569            Some(components.len().saturating_sub(1))
12570        }
12571        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
12572    };
12573    // Ordinary using-declarations participate in unqualified lookup at their
12574    // lexical scope. They therefore replace the resolver's terminal/global
12575    // fallback at the same or a shallower depth. A declaration in a more deeply
12576    // nested named scope is the closer lexical result and remains authoritative.
12577    // Ambiguous imports fail closed unless such a closer declaration exists.
12578    let ordinary_import_started = Instant::now();
12579    if report_stats {
12580        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
12581    }
12582    let ordinary_import_resolution = ordinary_type_import_resolution(
12583        node,
12584        components,
12585        global,
12586        analyzer,
12587        visibility,
12588        ordinary_type_imports,
12589        file,
12590        source,
12591        &lexical_scope,
12592        direct_target,
12593    );
12594    if report_stats {
12595        eprintln!(
12596            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
12597            ordinary_import_started.elapsed().as_millis(),
12598        );
12599    }
12600    let resolution = match ordinary_import_resolution {
12601        OrdinaryTypeImportResolution::Missing => normal,
12602        OrdinaryTypeImportResolution::Resolved {
12603            lexical_depth,
12604            is_direct,
12605            ..
12606        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
12607            || normal_depth.is_some_and(|depth| {
12608                depth > lexical_depth || (!is_direct && depth == lexical_depth)
12609            }) =>
12610        {
12611            normal
12612        }
12613        OrdinaryTypeImportResolution::Resolved {
12614            target,
12615            target_components,
12616            ..
12617        } => visibility.resolve_imported_type_candidate(
12618            analyzer,
12619            file,
12620            &target,
12621            &target_components,
12622            direct_target,
12623            preserve_alias,
12624        ),
12625        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
12626            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
12627        {
12628            normal
12629        }
12630        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12631    };
12632    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12633        return resolution;
12634    }
12635
12636    // A foreign header can contribute an exact using binding from a build
12637    // configuration that is compatible with, but not implied by, the current
12638    // reference. Forward navigation has no "unproven" result channel, so it
12639    // may still navigate through one unique structured import after ordinary
12640    // lookup misses. Inverse attribution keeps using its target-guided path
12641    // and records the same evidence as unproven (#940).
12642    match compatible_foreign_type_import_resolution(
12643        node,
12644        components,
12645        global,
12646        analyzer,
12647        visibility,
12648        ordinary_type_imports,
12649        file,
12650        source,
12651        &lexical_scope,
12652        None,
12653    ) {
12654        OrdinaryTypeImportResolution::Missing => resolution,
12655        OrdinaryTypeImportResolution::Resolved {
12656            target,
12657            target_components,
12658            ..
12659        } => visibility.resolve_imported_type_candidate(
12660            analyzer,
12661            file,
12662            &target,
12663            &target_components,
12664            None,
12665            true,
12666        ),
12667        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12668    }
12669}
12670
12671fn recovered_same_file_type_alias_resolution(
12672    node: Node<'_>,
12673    components: &[String],
12674    analyzer: &CppGraphSource<'_>,
12675    visibility: &VisibilityIndex<'_>,
12676    file: &ProjectFile,
12677    direct_target: Option<&CodeUnit>,
12678    lexical_scope: &[String],
12679) -> Option<LexicalTypeResolution> {
12680    debug_assert_eq!(components.len(), 1);
12681    debug_assert!(recovered_macro_decorated_type_node(node).is_some());
12682    let alias_provider = analyzer.type_alias_provider()?;
12683    for qualified in lexical_component_tiers(components, false, lexical_scope) {
12684        let candidates = visibility
12685            .visible_identifier_candidates(file, &components[0])
12686            .filter(|candidate| {
12687                candidate.source() == file
12688                    && canonical_cpp_scope_components(candidate) == qualified
12689                    && visibility
12690                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12691            })
12692            .collect::<Vec<_>>();
12693        if candidates.is_empty() {
12694            continue;
12695        }
12696        if candidates
12697            .iter()
12698            .any(|candidate| !alias_provider.is_type_alias(candidate))
12699        {
12700            return None;
12701        }
12702        let unit = if let Some(target) = direct_target {
12703            visibility.unique_type_candidate_preserving_target(
12704                analyzer,
12705                file,
12706                &candidates,
12707                target,
12708            )?
12709        } else {
12710            let first = candidates[0];
12711            if candidates
12712                .iter()
12713                .any(|candidate| !same_visible_symbol(candidate, first))
12714            {
12715                return None;
12716            }
12717            first.clone()
12718        };
12719        return Some(LexicalTypeResolution::Resolved {
12720            unit,
12721            components: qualified,
12722            candidates: candidates.into_iter().cloned().collect(),
12723        });
12724    }
12725    None
12726}
12727
12728fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12729    matches!(
12730        structured_owner_context_resolution(node, ctx),
12731        StructuredOwnerContextResolution::SelfTarget
12732            | StructuredOwnerContextResolution::InheritedTarget
12733    )
12734}
12735
12736/// A bare/`this->` member call whose name resolves, through the enclosing class's base
12737/// hierarchy or through a lexically enclosing class, to the target member declared on
12738/// another owner. This is a genuine external usage of that owner's member rather than a
12739/// same-type self call.
12740fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12741    let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
12742        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
12743    }) else {
12744        return matches!(
12745            structured_owner_context_resolution(node, ctx),
12746            StructuredOwnerContextResolution::InheritedTarget
12747        );
12748    };
12749    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12750        return false;
12751    };
12752    let chain = structured_enclosing_owner_chain(node, ctx);
12753    let Some(innermost) = chain.first() else {
12754        return false;
12755    };
12756    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12757        return false;
12758    }
12759    let Some(arity) = ctx
12760        .visibility
12761        .call_arity_evidence(ctx.file, call, ctx.source)
12762        .exact()
12763    else {
12764        return false;
12765    };
12766    for enclosing_owner in &chain {
12767        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12768            return true;
12769        }
12770        match resolve_declaring_callable_owner(
12771            &ctx.analyzer,
12772            ctx.visibility,
12773            ctx.file,
12774            cached_declaring_member_owner(enclosing_owner, ctx),
12775            &ctx.spec.member_name,
12776            arity,
12777        ) {
12778            EnclosingMemberOwnerResolution::Owner(owner) => {
12779                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12780            }
12781            EnclosingMemberOwnerResolution::Ambiguous => return false,
12782            // The name is not declared in this class or its bases, so C++
12783            // unqualified lookup continues in the next enclosing class (#3095).
12784            EnclosingMemberOwnerResolution::Missing => {}
12785        }
12786    }
12787    false
12788}
12789
12790fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12791    matches!(
12792        structured_owner_context_resolution(node, ctx),
12793        StructuredOwnerContextResolution::NonTarget
12794    )
12795}
12796
12797fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12798    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12799        return false;
12800    };
12801    let mut current = ctx.ancestry.parent(node);
12802    while let Some(parent) = current {
12803        if parent.kind() == "function_definition" {
12804            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
12805                return false;
12806            };
12807            if let Some(owners) = out_of_line_member_definition_owner(
12808                &ctx.analyzer,
12809                ctx.visibility,
12810                ctx.file,
12811                ctx.source,
12812                owner_lookup,
12813            ) && let Some((_, owner)) = owners.innermost()
12814            {
12815                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
12816            }
12817            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
12818                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12819            }
12820            return false;
12821        }
12822        current = ctx.ancestry.parent(parent);
12823    }
12824    false
12825}
12826
12827#[derive(Clone, Copy)]
12828enum StructuredOwnerContextResolution {
12829    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
12830    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
12831    SelfTarget,
12832    /// The enclosing class does not declare the member; lookup reaches the target owner
12833    /// through a base of that class, or through a lexically enclosing class (a nested
12834    /// class calling the outer class's member). Either way the reference is a genuine
12835    /// external usage OF that owner's member (e.g. `Derived` calling inherited
12836    /// `Base::value`), not a self call, so it is attributed as an ordinary Reference.
12837    InheritedTarget,
12838    NonTarget,
12839    Ambiguous,
12840    Missing,
12841}
12842
12843fn structured_owner_context_resolution(
12844    node: Node<'_>,
12845    ctx: &ScanCtx<'_>,
12846) -> StructuredOwnerContextResolution {
12847    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12848        return StructuredOwnerContextResolution::Missing;
12849    };
12850    let chain = structured_enclosing_owner_chain(node, ctx);
12851    let Some(innermost) = chain.first() else {
12852        return StructuredOwnerContextResolution::Missing;
12853    };
12854    if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
12855        return StructuredOwnerContextResolution::SelfTarget;
12856    }
12857    // Unqualified lookup searches the enclosing class and its bases, then each lexically
12858    // enclosing class outward, and stops at the first scope that declares the name (#3095).
12859    // Only the immediately enclosing class makes a reference a same-type self call; any
12860    // other owner it reaches is a genuine usage of that owner's member.
12861    for enclosing_owner in &chain {
12862        if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
12863            return StructuredOwnerContextResolution::InheritedTarget;
12864        }
12865        match cached_declaring_member_owner(enclosing_owner, ctx) {
12866            EnclosingMemberOwnerResolution::Owner(owner)
12867                if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
12868            {
12869                return StructuredOwnerContextResolution::InheritedTarget;
12870            }
12871            EnclosingMemberOwnerResolution::Owner(_) => {
12872                return StructuredOwnerContextResolution::NonTarget;
12873            }
12874            EnclosingMemberOwnerResolution::Ambiguous => {
12875                return StructuredOwnerContextResolution::Ambiguous;
12876            }
12877            EnclosingMemberOwnerResolution::Missing => {}
12878        }
12879    }
12880    StructuredOwnerContextResolution::Missing
12881}
12882
12883fn cached_declaring_member_owner(
12884    receiver_owner: &CodeUnit,
12885    ctx: &ScanCtx<'_>,
12886) -> EnclosingMemberOwnerResolution {
12887    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
12888        return cached;
12889    }
12890    let resolved = resolve_declaring_member_owner(
12891        &ctx.analyzer,
12892        ctx.visibility,
12893        ctx.file,
12894        receiver_owner,
12895        &ctx.spec.member_name,
12896    );
12897    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
12898        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
12899    } else {
12900        resolved
12901    };
12902    ctx.member_owner_cache
12903        .borrow_mut()
12904        .insert(receiver_owner.clone(), resolved.clone());
12905    resolved
12906}
12907
12908/// Recover a member's declaring owner when parser recovery omitted its
12909/// in-class declaration but retained an out-of-line definition. Ordinary
12910/// visible-member lookup runs first. The structured definition index then
12911/// supplies the missing member fact at each hierarchy level, so an indexed
12912/// derived override still hides the queried base member and distinct base
12913/// paths still fail closed.
12914fn indexed_declaring_owner_for_recovered_member(
12915    receiver_owner: &CodeUnit,
12916    ctx: &ScanCtx<'_>,
12917) -> EnclosingMemberOwnerResolution {
12918    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
12919        return EnclosingMemberOwnerResolution::Missing;
12920    };
12921    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
12922        return EnclosingMemberOwnerResolution::Missing;
12923    }
12924    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
12925        return EnclosingMemberOwnerResolution::Missing;
12926    };
12927    let Some(receiver_owner) =
12928        ctx.visibility
12929            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
12930    else {
12931        return EnclosingMemberOwnerResolution::Ambiguous;
12932    };
12933    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
12934        ctx.visibility
12935            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
12936    }) else {
12937        return EnclosingMemberOwnerResolution::Missing;
12938    };
12939
12940    let owner_declares_member = |owner: &CodeUnit| {
12941        if same_visible_symbol(owner, &target_owner) {
12942            return true;
12943        }
12944        let mut member_fq = owner.fq().clone();
12945        member_fq.push(
12946            ctx.spec
12947                .target
12948                .fq()
12949                .last()
12950                .expect("a method target has a terminal member segment"),
12951        );
12952        ctx.analyzer
12953            .definitions(&member_fq.display(segment_interner()))
12954            .any(|child| child.is_function())
12955    };
12956    if owner_declares_member(&receiver_owner) {
12957        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
12958    }
12959
12960    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
12961    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
12962    let mut declaring_owner = None;
12963    while let Some(raw_owner) = stack.pop() {
12964        let Some(owner) =
12965            ctx.visibility
12966                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
12967        else {
12968            return EnclosingMemberOwnerResolution::Ambiguous;
12969        };
12970        let propagated = propagated_counts.entry(owner.clone()).or_default();
12971        if *propagated == 2 {
12972            continue;
12973        }
12974        *propagated += 1;
12975        if owner_declares_member(&owner) {
12976            if declaring_owner.is_some() {
12977                return EnclosingMemberOwnerResolution::Ambiguous;
12978            }
12979            declaring_owner = Some(owner);
12980            continue;
12981        }
12982        stack.extend(hierarchy.get_direct_ancestors(&owner));
12983    }
12984    declaring_owner
12985        .map(EnclosingMemberOwnerResolution::Owner)
12986        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
12987}
12988
12989/// The lexically enclosing classes at a reference, innermost first: C++ unqualified
12990/// lookup searches each in turn before it reaches the enclosing namespaces.
12991fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
12992    let Some(innermost) = structured_enclosing_owner(node, ctx) else {
12993        return Vec::new();
12994    };
12995    // The innermost owner is whatever enclosing-owner resolution produced, so
12996    // it is kept as given; only the walk outward is filtered to classes.
12997    let mut chain = vec![innermost.clone()];
12998    chain.extend(
12999        brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(innermost, |unit| {
13000            ctx.analyzer.parent_of(unit)
13001        })
13002        .skip(1)
13003        .take_while(CodeUnit::is_class),
13004    );
13005    chain
13006}
13007
13008fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13009    // Declaration recovery can index the true class/member ranges even when
13010    // the original error tree wraps that region in a bogus function. Prefer
13011    // the analyzer's exact enclosing-owner graph at the reference byte before
13012    // interpreting such a wrapper as a real callable owner.
13013    if (has_recovered_class_shape_ancestor(node)
13014        || has_malformed_wrapper_function_definition_ancestor(node))
13015        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13016    {
13017        return Some(owner);
13018    }
13019    let mut current = ctx.ancestry.parent(node);
13020    while let Some(parent) = current {
13021        if parent.kind() == "function_definition" {
13022            let owner_lookup = function_definition_owner_lookup_node(parent);
13023            if let Some(owner_lookup) = owner_lookup
13024                && let Some(owners) = out_of_line_member_definition_owner(
13025                    &ctx.analyzer,
13026                    ctx.visibility,
13027                    ctx.file,
13028                    ctx.source,
13029                    owner_lookup,
13030                )
13031                && let Some((_, owner)) = owners.innermost()
13032            {
13033                return Some(owner.clone());
13034            }
13035            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13036                return Some(owner);
13037            }
13038            if let Some(owner) = enclosing_context(parent, ctx)
13039                .owner
13040                .filter(|owner| owner.is_class())
13041            {
13042                return Some(owner);
13043            }
13044            if let Some(owner_lookup) = owner_lookup
13045                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13046            {
13047                return Some(owner);
13048            }
13049            break;
13050        }
13051        current = ctx.ancestry.parent(parent);
13052    }
13053    enclosing_context(node, ctx)
13054        .owner
13055        .filter(|owner| owner.is_class())
13056}
13057
13058fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13059    let target_owner = ctx.spec.owner.as_ref()?;
13060    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13061    let owner_name = owner_components.last()?;
13062    let mut candidates = Vec::new();
13063    for candidate in ctx
13064        .visibility
13065        .visible_identifier_candidates(ctx.file, owner_name)
13066        .filter(|candidate| candidate.is_class())
13067    {
13068        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13069            brokk_bifrost_core::analyzer::Language::Cpp,
13070            &cpp_name_for(candidate),
13071        );
13072        if !components.ends_with(&owner_components)
13073            || candidates
13074                .iter()
13075                .any(|existing| same_logical_symbol(existing, candidate))
13076        {
13077            continue;
13078        }
13079        candidates.push(candidate.clone());
13080    }
13081    let [candidate] = candidates.as_slice() else {
13082        return None;
13083    };
13084    (same_logical_symbol(candidate, target_owner)
13085        && target_group_contains_owner_peer(candidate, ctx))
13086    .then(|| candidate.clone())
13087}