Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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