Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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