Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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