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