Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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