Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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