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