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