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