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