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