Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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