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