Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

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