Skip to main content

brokk_bifrost_cpp/graph/
extractor.rs

1use crate::call_match::{
2    CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_forwarding_call_argument,
3    cpp_literal_arg_type, cpp_signature_param_types, cpp_type_text_pointer_depth,
4    normalize_cpp_type_name,
5};
6use crate::declarations::{
7    CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
8    cpp_sentinel_recovered_scope_for_node, is_recovered_exported_class_base_type_node, node_text,
9    normalize_cpp_whitespace, recovered_macro_return_type_node,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
13use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
14use crate::graph::hits::{
15    enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
16    push_definition_hit, push_hit, push_recursive_reference_hit, push_reference_hit_range,
17    push_self_receiver_hit, push_type_hit, push_type_hit_range, push_unproven_definition_hit,
18    push_unproven_hit, push_unproven_reference_hit_range,
19};
20use crate::graph::resolver::*;
21use crate::graph::syntax::{object_macro_replacement_type_references, qualified_callable_value};
22use crate::graph_support::CppSource;
23use brokk_bifrost_core::analyzer::fq_name::segment_interner;
24use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
25use brokk_bifrost_core::analyzer::query_token::QueryToken;
26use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
27use brokk_bifrost_core::analyzer::usages::common::same_node;
28use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
29use brokk_bifrost_core::analyzer::usages::local_inference::{
30    LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
31};
32use brokk_bifrost_core::analyzer::usages::model::UsageHit;
33use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
34use brokk_bifrost_core::hash::{HashMap, HashSet};
35#[cfg(any(test, feature = "test-support"))]
36use std::cell::Cell;
37use std::cell::{OnceCell, RefCell};
38use std::collections::BTreeSet;
39use std::sync::Arc;
40use std::time::Instant;
41use tree_sitter::Node;
42
43#[cfg(any(test, feature = "test-support"))]
44thread_local! {
45    pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
46}
47
48pub struct ScanState<'a> {
49    pub max_usages: usize,
50    pub hits: &'a mut BTreeSet<UsageHit>,
51    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
52    pub raw_match_count: &'a mut usize,
53    pub limit_exceeded: &'a mut bool,
54}
55
56pub struct ScanCtx<'a> {
57    pub analyzer: CppGraphSource<'a>,
58    pub visibility: &'a VisibilityIndex<'a>,
59    pub file: &'a ProjectFile,
60    pub source: &'a str,
61    ordinary_type_imports: OrdinaryTypeImportCell,
62    recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
63    class_ranges: Option<&'a ClassRangeIndex>,
64    pub line_starts: &'a [usize],
65    pub spec: &'a TargetSpec,
66    pub target_group: &'a HashSet<CodeUnit>,
67    pub has_physically_visible_type_target: bool,
68    type_reference_component_names: HashSet<String>,
69    pub target_declaration_ranges: Vec<Range>,
70    orphaned_namespaces: Vec<OrphanedNamespaceEnvelope>,
71    orphaned_namespace_scopes: OnceCell<OrphanedNamespaceTypeScopeIndex>,
72    pub bindings: LocalInferenceEngine<CppScanBinding>,
73    local_shadows: LocalInferenceEngine<()>,
74    using_enum_owners: ScopedUsingEnumOwners,
75    semantic_using_enum_owners: SemanticUsingEnumOwners,
76    needs_using_enum_member_resolution: bool,
77    pub hits: &'a mut BTreeSet<UsageHit>,
78    pub unproven_hits: &'a mut BTreeSet<UsageHit>,
79    pub raw_match_count: &'a mut usize,
80    pub max_usages: usize,
81    pub limit_exceeded: &'a mut bool,
82    pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
83    pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
84    lexical_scope_cache: LexicalScopeCache,
85    lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
86    member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
87    global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
88    receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
89}
90
91impl ScanCtx<'_> {
92    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
93        cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
94    }
95}
96
97#[derive(Clone, Default)]
98pub struct EnclosingContext {
99    pub enclosing: Option<CodeUnit>,
100    pub owner: Option<CodeUnit>,
101}
102
103pub fn prepare_file(
104    cpp: &dyn CppSource,
105    token: QueryToken<'_>,
106    file: &ProjectFile,
107) -> Option<Arc<PreparedSyntaxTree>> {
108    cpp.prepared_syntax(token, file)
109}
110
111#[allow(clippy::too_many_arguments)]
112pub fn scan_prepared_file(
113    analyzer: &CppGraphSource<'_>,
114    visibility: &VisibilityIndex<'_>,
115    file: &ProjectFile,
116    prepared: &PreparedSyntaxTree,
117    recovered_sentinel_classes: &[CppSentinelRecoveredClass],
118    class_ranges: Option<&ClassRangeIndex>,
119    spec: &TargetSpec,
120    target_group: &HashSet<CodeUnit>,
121    state: &mut ScanState<'_>,
122) {
123    if *state.limit_exceeded {
124        return;
125    }
126    let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
127    let has_physically_visible_type_target = spec.kind == TargetKind::Type
128        && target_group.iter().any(|target| {
129            same_logical_symbol(target, &spec.target)
130                && visibility.is_physically_visible(file, target)
131        });
132    if spec.kind == TargetKind::Type
133        && !has_physically_visible_type_target
134        && visibility
135            .visible_identifier_candidates(file, spec.target.identifier())
136            .any(|candidate| {
137                candidate != &spec.target
138                    && !target_group.contains(candidate)
139                    && same_logical_symbol(candidate, &spec.target)
140                    && visibility.is_physically_visible(file, candidate)
141            })
142    {
143        return;
144    }
145    let target_declaration_ranges = if spec.kind == TargetKind::Type {
146        target_group
147            .iter()
148            .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
149            .flat_map(|target| analyzer.ranges(target))
150            .collect()
151    } else if spec.target.source() == file {
152        analyzer.ranges(&spec.target)
153    } else {
154        Vec::new()
155    };
156    let ordinary_type_imports = initialized_ordinary_type_imports(
157        prepared.tree().root_node(),
158        analyzer,
159        visibility,
160        file,
161        prepared.source(),
162    );
163    let type_reference_component_names = if spec.kind == TargetKind::Type {
164        visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
165    } else {
166        HashSet::default()
167    };
168    let orphaned_namespaces = if spec.kind == TargetKind::Type
169        && prepared.tree().root_node().has_error()
170    {
171        collect_orphaned_namespace_type_envelopes(prepared.tree().root_node(), prepared.source())
172    } else {
173        Vec::new()
174    };
175    let mut ctx = ScanCtx {
176        analyzer: *analyzer,
177        visibility,
178        file,
179        source: prepared.source(),
180        ordinary_type_imports,
181        recovered_sentinel_classes,
182        class_ranges,
183        line_starts: prepared.line_starts(),
184        spec,
185        target_group,
186        has_physically_visible_type_target,
187        type_reference_component_names,
188        target_declaration_ranges,
189        orphaned_namespaces,
190        orphaned_namespace_scopes: OnceCell::new(),
191        bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
192        local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
193        using_enum_owners: ScopedUsingEnumOwners::new(),
194        semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
195        needs_using_enum_member_resolution,
196        hits: state.hits,
197        unproven_hits: state.unproven_hits,
198        raw_match_count: state.raw_match_count,
199        max_usages: state.max_usages,
200        limit_exceeded: state.limit_exceeded,
201        enclosing_cache: RefCell::new(HashMap::default()),
202        enclosing_owner_cache: RefCell::new(HashMap::default()),
203        lexical_scope_cache: RefCell::new(HashMap::default()),
204        lexical_free_function_cache: RefCell::new(HashMap::default()),
205        member_owner_cache: RefCell::new(HashMap::default()),
206        global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
207        receiver_canonical_type_cache: RefCell::new(HashMap::default()),
208    };
209    if needs_using_enum_member_resolution {
210        collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
211    }
212    scan_node(prepared.tree().root_node(), &mut ctx);
213}
214
215enum UsingEnumDeclarationScope {
216    Block,
217    Class(CodeUnit),
218    Namespace(Vec<String>),
219    UnsupportedClass,
220}
221
222fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
223    let mut current = node.parent();
224    while let Some(parent) = current {
225        if matches!(
226            parent.kind(),
227            "compound_statement"
228                | "function_definition"
229                | "lambda_expression"
230                | "for_statement"
231                | "while_statement"
232                | "if_statement"
233        ) {
234            return UsingEnumDeclarationScope::Block;
235        }
236        if matches!(
237            parent.kind(),
238            "class_specifier" | "struct_specifier" | "union_specifier"
239        ) {
240            let resolution = enclosing_lexical_scope_components(
241                node,
242                &ctx.analyzer,
243                ctx.visibility,
244                ctx.file,
245                ctx.source,
246            );
247            if let LexicalScopeResolution::Resolved(components) = resolution
248                && let LexicalTypeResolution::Resolved { unit, .. } =
249                    ctx.visibility.resolve_type_components_lexically(
250                        &ctx.analyzer,
251                        ctx.file,
252                        &components,
253                        true,
254                        &[],
255                    )
256            {
257                return UsingEnumDeclarationScope::Class(unit);
258            }
259            return UsingEnumDeclarationScope::UnsupportedClass;
260        }
261        current = parent.parent();
262    }
263    UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
264}
265
266fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
267    let mut stack = vec![root];
268    while let Some(node) = stack.pop() {
269        if node.kind() == "using_declaration"
270            && let LexicalTypeResolution::Resolved { unit, .. } =
271                resolve_using_enum_declaration_owner(
272                    node,
273                    &ctx.analyzer,
274                    ctx.visibility,
275                    &ctx.ordinary_type_imports,
276                    ctx.file,
277                    ctx.source,
278                )
279        {
280            match using_enum_declaration_scope(node, ctx) {
281                UsingEnumDeclarationScope::Block => {}
282                UsingEnumDeclarationScope::Class(class) => {
283                    ctx.semantic_using_enum_owners.import_class(class, unit);
284                }
285                UsingEnumDeclarationScope::Namespace(namespace) => {
286                    ctx.semantic_using_enum_owners.import_namespace(
287                        namespace,
288                        node.start_byte(),
289                        unit,
290                    );
291                }
292                UsingEnumDeclarationScope::UnsupportedClass => {}
293            }
294        }
295        for index in (0..node.named_child_count()).rev() {
296            if let Some(child) = node.named_child(index) {
297                stack.push(child);
298            }
299        }
300    }
301}
302
303fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
304    if *ctx.limit_exceeded {
305        return;
306    }
307    let enters_scope = matches!(
308        node.kind(),
309        "compound_statement"
310            | "function_definition"
311            | "lambda_expression"
312            | "for_statement"
313            | "for_range_loop"
314            | "while_statement"
315            | "if_statement"
316    );
317    let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
318        && (enters_scope
319            || matches!(
320                node.kind(),
321                "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
322            ));
323    if enters_scope {
324        ctx.bindings.enter_scope();
325        ctx.local_shadows.enter_scope();
326    }
327    if enters_using_enum_scope {
328        ctx.using_enum_owners.enter_scope();
329    }
330
331    seed_declarations(node, ctx);
332    maybe_record_hit(node, ctx);
333
334    let translation_unit = node.kind() == "translation_unit";
335    let mut fractured_function_scope = false;
336    let mut cursor = node.walk();
337    for child in node.named_children(&mut cursor) {
338        if translation_unit && fractured_function_scope && child.kind() == "ERROR" {
339            ctx.bindings.exit_scope();
340            ctx.local_shadows.exit_scope();
341            fractured_function_scope = false;
342        }
343        scan_node(child, ctx);
344        if *ctx.limit_exceeded {
345            break;
346        }
347        if translation_unit
348            && macro_fractured_function(child)
349            && macro_fracture_boundary(child).is_some()
350        {
351            if fractured_function_scope {
352                ctx.bindings.exit_scope();
353                ctx.local_shadows.exit_scope();
354            }
355            ctx.bindings.enter_scope();
356            ctx.local_shadows.enter_scope();
357            seed_fractured_function_prefix(child, ctx);
358            fractured_function_scope = true;
359        }
360    }
361    if fractured_function_scope {
362        ctx.bindings.exit_scope();
363        ctx.local_shadows.exit_scope();
364    }
365
366    if enters_scope {
367        ctx.bindings.exit_scope();
368        ctx.local_shadows.exit_scope();
369    }
370    if enters_using_enum_scope {
371        ctx.using_enum_owners.exit_scope();
372    }
373}
374
375fn macro_fractured_function(node: Node<'_>) -> bool {
376    if node.kind() != "function_definition" {
377        return false;
378    }
379    let Some(body) = node.child_by_field_name("body") else {
380        return false;
381    };
382    let mut stack = vec![body];
383    while let Some(current) = stack.pop() {
384        if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
385            return true;
386        }
387        let mut cursor = current.walk();
388        stack.extend(current.named_children(&mut cursor));
389    }
390    false
391}
392
393fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
394    let first_orphan = node.next_named_sibling()?;
395    if !matches!(
396        first_orphan.kind(),
397        "expression_statement"
398            | "if_statement"
399            | "for_statement"
400            | "while_statement"
401            | "do_statement"
402            | "return_statement"
403    ) {
404        return None;
405    }
406    let mut sibling = Some(first_orphan);
407    while let Some(current) = sibling {
408        if current.kind() == "ERROR" {
409            return Some(current.end_byte());
410        }
411        if matches!(
412            current.kind(),
413            "function_definition" | "declaration" | "type_definition"
414        ) {
415            return None;
416        }
417        sibling = current.next_named_sibling();
418    }
419    None
420}
421
422fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
423    let cutoff = node.end_byte().saturating_sub(1);
424    seed_fractured_active_path(node, cutoff, ctx);
425}
426
427fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
428    if node.start_byte() >= cutoff {
429        return;
430    }
431    let enters_scope = matches!(
432        node.kind(),
433        "compound_statement"
434            | "function_definition"
435            | "lambda_expression"
436            | "for_range_loop"
437            | "for_statement"
438            | "while_statement"
439            | "if_statement"
440            | "class_specifier"
441            | "struct_specifier"
442            | "union_specifier"
443    );
444    if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
445        return;
446    }
447    seed_declarations(node, ctx);
448    let mut cursor = node.walk();
449    for child in node.named_children(&mut cursor) {
450        if child.start_byte() >= cutoff {
451            break;
452        }
453        seed_fractured_active_path(child, cutoff, ctx);
454    }
455}
456
457fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
458    if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
459        || (ctx.spec.kind != TargetKind::Type
460            && indexed_recovered_class_field_declaration(node, ctx))
461    {
462        return;
463    }
464    match node.kind() {
465        "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
466        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
467        "for_range_loop" => seed_range_binding(node, ctx),
468        "expression_statement" => seed_function_macro_local_binding(node, ctx),
469        "using_declaration" => seed_using_enum(node, ctx),
470        _ => {}
471    }
472}
473
474fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
475    let Some(binding) = ctx
476        .visibility
477        .function_macro_local_binding(ctx.file, node, ctx.source)
478    else {
479        return;
480    };
481    if ctx.spec.kind == TargetKind::Type {
482        ctx.bindings.declare_shadow(binding.name);
483        return;
484    }
485    let normalized = normalize_cpp_type_name(&binding.type_name);
486    let unit = binding
487        .type_node
488        .and_then(|type_node| {
489            ctx.visibility
490                .resolve_type_node_result(ctx.file, type_node, ctx.source)
491                .ok()
492                .flatten()
493        })
494        .or_else(|| {
495            ctx.visibility
496                .canonical_type_for_reference(ctx.file, &normalized)
497        })
498        .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
499    ctx.bindings.seed_symbol(
500        binding.name,
501        CppScanBinding::from_type_name(
502            normalized,
503            unit,
504            binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
505        ),
506    );
507}
508
509fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
510    if node.kind() != "declaration"
511        || !has_function_scope_ancestor(node)
512        || has_ancestor_kind(node, "lambda_expression")
513        || !(has_recovered_class_shape_ancestor(node)
514            || has_malformed_wrapper_function_definition_ancestor(node))
515    {
516        return false;
517    }
518    let context = enclosing_context(node, ctx);
519    context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
520        && context.owner.as_ref().is_some_and(CodeUnit::is_class)
521}
522
523fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
524    if !ctx.needs_using_enum_member_resolution {
525        return;
526    }
527    if let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
528        node,
529        &ctx.analyzer,
530        ctx.visibility,
531        &ctx.ordinary_type_imports,
532        ctx.file,
533        ctx.source,
534    ) && matches!(
535        using_enum_declaration_scope(node, ctx),
536        UsingEnumDeclarationScope::Block
537    ) {
538        ctx.using_enum_owners.import(unit);
539    }
540}
541
542fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
543    // Class and local-struct fields are resolved from their enclosing owner
544    // when they appear as an unqualified receiver. Seeding them into the
545    // function-wide binding scope would make same-spelled fields from sibling
546    // owners overwrite one another before the owner-aware path runs.
547    if node.kind() == "field_declaration" {
548        return;
549    }
550    let type_node = node
551        .child_by_field_name("type")
552        .or_else(|| first_type_child(node));
553    let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
554    let mut cursor = node.walk();
555    for child in node.named_children(&mut cursor) {
556        let declarator = if child.kind() == "init_declarator" {
557            child.child_by_field_name("declarator")
558        } else if is_declarator_node(child) {
559            Some(child)
560        } else {
561            None
562        };
563        let Some(declarator) = declarator else {
564            continue;
565        };
566        let Some(name) = extract_variable_name(declarator, ctx.source) else {
567            continue;
568        };
569        if declarator.kind() == "function_declarator"
570            && !constructor_style_local_declaration(
571                ctx.visibility,
572                ctx.file,
573                ctx.source,
574                declarator,
575                type_text.as_deref(),
576                &ctx.bindings,
577            )
578        {
579            if node.kind() == "declaration" && has_function_scope_ancestor(node) {
580                ctx.local_shadows.declare_shadow(name);
581            }
582            continue;
583        }
584        if node.kind() == "declaration" && has_function_scope_ancestor(node) {
585            ctx.local_shadows.declare_shadow(name.clone());
586        }
587        if ctx.spec.kind == TargetKind::Type {
588            ctx.bindings.declare_shadow(name);
589            continue;
590        }
591        let value = child.child_by_field_name("value");
592        seed_binding_from_type_or_value(&name, type_node, value, ctx);
593    }
594}
595
596fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
597    if !parameter_belongs_to_callable_scope(node) {
598        return;
599    }
600    let Some(declarator) = node.child_by_field_name("declarator") else {
601        return;
602    };
603    let Some(name) = extract_variable_name(declarator, ctx.source) else {
604        return;
605    };
606    if has_function_scope_ancestor(node) {
607        ctx.local_shadows.declare_shadow(name.clone());
608    }
609    if ctx.spec.kind == TargetKind::Type {
610        ctx.bindings.declare_shadow(name);
611        return;
612    }
613    let type_node = node
614        .child_by_field_name("type")
615        .or_else(|| first_type_child(node));
616    seed_binding_from_type_or_value(&name, type_node, None, ctx);
617}
618
619fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
620    let Some(declarator) = node.child_by_field_name("declarator") else {
621        return;
622    };
623    let Some(name) = extract_variable_name(declarator, ctx.source) else {
624        return;
625    };
626    if has_function_scope_ancestor(node) {
627        ctx.local_shadows.declare_shadow(name.clone());
628    }
629    if ctx.spec.kind == TargetKind::Type {
630        ctx.bindings.declare_shadow(name);
631        return;
632    }
633    let type_node = node
634        .child_by_field_name("type")
635        .or_else(|| first_type_child(node));
636    seed_binding_from_type_or_value(&name, type_node, None, ctx);
637}
638
639fn has_function_scope_ancestor(node: Node<'_>) -> bool {
640    let mut current = node.parent();
641    while let Some(parent) = current {
642        // Error recovery can wrap a complete namespace in a bogus outer
643        // `function_definition` (for example when an object-like namespace
644        // macro is parsed as a return type).  Declarations below that
645        // namespace are still namespace-scoped: do not let the malformed
646        // callable envelope seed them as local shadows.  A real function
647        // body is encountered before its enclosing namespace, so this keeps
648        // ordinary local binding detection unchanged.
649        if parent.kind() == "namespace_definition" {
650            return false;
651        }
652        if parent.kind() == "function_definition" {
653            // The malformed sentinel envelope is not a callable scope. Its
654            // body may contain a recovered namespace (or, in a smaller error
655            // tree, only an ERROR node standing in for that namespace), so
656            // declarations directly below it must remain namespace-scoped.
657            // Real nested functions are encountered first and still seed
658            // ordinary local bindings.
659            return !is_malformed_wrapper_function_definition(parent);
660        }
661        if parent.kind() == "lambda_expression" {
662            return true;
663        }
664        current = parent.parent();
665    }
666    false
667}
668
669fn seed_binding_from_type_or_value(
670    name: &str,
671    type_node: Option<Node<'_>>,
672    value: Option<Node<'_>>,
673    ctx: &mut ScanCtx<'_>,
674) {
675    if name.is_empty() {
676        return;
677    }
678    let resolved = type_node
679        .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
680        .map(|node| {
681            let text = node_text(node, ctx.source);
682            let name = normalize_cpp_type_name(text);
683            // Bare type names need lexical ownership before the coarse visible-name
684            // fallback (two namespaces can each declare `CopyResult`). Template
685            // references keep the specialization-aware resolver first because a
686            // component-only lexical lookup cannot rank partial specializations.
687            let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
688                if cpp_template_reference_arguments(node, ctx.source).is_some() {
689                    return None;
690                }
691                match enclosing_lexical_scope_components(
692                    node,
693                    &ctx.analyzer,
694                    ctx.visibility,
695                    ctx.file,
696                    ctx.source,
697                ) {
698                    LexicalScopeResolution::Resolved(scope) => Some(scope),
699                    LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
700                }
701            });
702            let unit = lexical_scope
703                .as_deref()
704                .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
705                .or_else(|| {
706                    match ctx
707                        .visibility
708                        .resolve_type_node_result(ctx.file, node, ctx.source)
709                    {
710                        Ok(Some(unit)) => Some(unit),
711                        Ok(None) => ctx
712                            .visibility
713                            .canonical_type_for_reference(ctx.file, &name)
714                            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
715                        Err(_) => None,
716                    }
717                });
718            CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
719        })
720        .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
721
722    if let Some(resolved) = resolved {
723        ctx.bindings.seed_symbol(name.to_string(), resolved);
724    } else if let Some(value) = value
725        && value.kind() == "identifier"
726    {
727        ctx.bindings
728            .alias_symbol(name.to_string(), node_text(value, ctx.source));
729    } else {
730        ctx.bindings.declare_shadow(name.to_string());
731    }
732}
733
734fn resolve_seed_type_node_lexically(
735    node: Node<'_>,
736    ctx: &ScanCtx<'_>,
737    scope: &[String],
738) -> Option<CodeUnit> {
739    let (components, global) = type_reference_components(node, ctx.source)?;
740    match ctx.visibility.resolve_type_components_lexically(
741        &ctx.analyzer,
742        ctx.file,
743        &components,
744        global,
745        scope,
746    ) {
747        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
748        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
749    }
750}
751
752const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
753
754fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
755    infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
756}
757
758fn infer_type_from_value_with_budget(
759    node: Node<'_>,
760    ctx: &ScanCtx<'_>,
761    remaining_call_depth: usize,
762) -> Option<CppScanBinding> {
763    match node.kind() {
764        "new_expression" | "call_expression" if remaining_call_depth == 0 => {
765            infer_cpp_initializer_binding(
766                &ctx.analyzer,
767                ctx.visibility,
768                ctx.file,
769                ctx.source,
770                node,
771                None,
772            )
773        }
774        "new_expression" | "call_expression" => infer_cpp_initializer_binding(
775            &ctx.analyzer,
776            ctx.visibility,
777            ctx.file,
778            ctx.source,
779            node,
780            Some(&|receiver, source| {
781                receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
782            }),
783        ),
784        "initializer_list" => None,
785        "identifier" => {
786            let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
787            resolved
788                .as_precise()?
789                .iter()
790                .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
791                .cloned()
792        }
793        _ => {
794            let text = node_text(node, ctx.source);
795            let name = normalize_cpp_type_name(text);
796            ctx.visibility
797                .resolve_type(ctx.file, &name)
798                .map(|unit| CppScanBinding::from_unit(unit, 0))
799        }
800    }
801}
802
803fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
804    match ctx.spec.kind {
805        TargetKind::Type => maybe_record_type_hit(node, ctx),
806        TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
807        TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
808        TargetKind::Method => maybe_record_method_hit(node, ctx),
809        TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
810        TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
811        TargetKind::Macro => maybe_record_macro_hit(node, ctx),
812    }
813}
814
815fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
816    if node_text(node, ctx.source) != ctx.spec.member_name {
817        return;
818    }
819    if is_ordinary_macro_reference_node(node) {
820        match ctx.visibility.resolve_ordinary_macro_reference(
821            &ctx.analyzer,
822            ctx.file,
823            node,
824            ctx.source,
825        ) {
826            OrdinaryMacroReferenceResolution::Resolved(unit)
827                if ctx.target_group.contains(&unit) =>
828            {
829                *ctx.raw_match_count += 1;
830                push_hit(node, ctx);
831            }
832            OrdinaryMacroReferenceResolution::Ambiguous
833                if ctx
834                    .visibility
835                    .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
836            {
837                *ctx.raw_match_count += 1;
838                push_unproven_hit(node, ctx);
839            }
840            OrdinaryMacroReferenceResolution::Resolved(_)
841            | OrdinaryMacroReferenceResolution::Ambiguous
842            | OrdinaryMacroReferenceResolution::Missing => {}
843        }
844        return;
845    }
846    if !matches!(
847        node.kind(),
848        "identifier"
849            | "field_identifier"
850            | "type_identifier"
851            | "namespace_identifier"
852            | "preproc_arg"
853    ) || node.parent().is_some_and(|parent| {
854        matches!(parent.kind(), "preproc_def" | "preproc_function_def")
855            && parent
856                .child_by_field_name("name")
857                .is_some_and(|name| same_node(name, node))
858    }) {
859        return;
860    }
861    if ctx.visibility.macro_binding_matches_target_at(
862        &ctx.analyzer,
863        ctx.file,
864        &ctx.spec.member_name,
865        node.start_byte(),
866        &ctx.spec.target,
867    ) {
868        *ctx.raw_match_count += 1;
869        push_hit(node, ctx);
870    } else if ctx.visibility.macro_name_may_be_bound_at(
871        ctx.file,
872        &ctx.spec.member_name,
873        node.start_byte(),
874    ) && ctx
875        .visibility
876        .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
877    {
878        *ctx.raw_match_count += 1;
879        push_unproven_hit(node, ctx);
880    }
881}
882
883fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
884    if node.kind() == "preproc_arg" {
885        maybe_record_object_macro_replacement_type_hits(node, ctx);
886        return;
887    }
888    let recovered_exported_class_base =
889        is_recovered_exported_class_base_type_node(node, ctx.source);
890    if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
891        maybe_record_recovered_macro_return_type_hit(return_type, ctx);
892        return;
893    }
894    if let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source) {
895        // A member-pointer owner can itself end in a nested alias, as in
896        // `type_identity<T>::type::*`. Resolving the complete owner
897        // canonicalizes that alias to its underlying type and loses the alias
898        // declaration that inverse lookup is targeting. Retain every
899        // structurally proven qualifier component before asking for the
900        // canonical owner type.
901        if ctx
902            .analyzer
903            .type_alias_provider()
904            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
905            && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
906            && let Some(terminal) = owner.nodes.last().copied()
907        {
908            if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
909                return;
910            }
911            if ctx.visibility.external_type_candidate_visible_in_context(
912                &ctx.analyzer,
913                ctx.file,
914                &ctx.spec.target,
915                terminal,
916            ) || ctx
917                .visibility
918                .dependent_member_pointer_alias_visible_in_context(
919                    &ctx.analyzer,
920                    ctx.file,
921                    &ctx.spec.target,
922                    &owner.names,
923                    terminal,
924                )
925            {
926                *ctx.raw_match_count += 1;
927                push_type_hit(terminal, ctx);
928            }
929            return;
930        }
931        if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
932            *ctx.raw_match_count += 1;
933            for scope in scopes {
934                push_type_hit(scope, ctx);
935            }
936            return;
937        }
938        return;
939    }
940    if node.kind() == "pointer_expression"
941        && let Some(value) = qualified_callable_value(node)
942        && let Some(scope) =
943            target_guided_unproven_qualified_value_owner_scope(value.qualified, ctx)
944    {
945        *ctx.raw_match_count += 1;
946        push_unproven_hit(scope, ctx);
947        return;
948    }
949    if node.kind() == "call_expression" {
950        maybe_record_direct_temporary_type_hit(node, ctx);
951        return;
952    }
953    if node.parent().is_some_and(|parent| {
954        parent.kind() == "operator_cast"
955            && parent
956                .child_by_field_name("type")
957                .is_some_and(|target| same_node(target, node))
958    }) {
959        return;
960    }
961    if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
962        *ctx.raw_match_count += 1;
963        push_type_hit(hit, ctx);
964        return;
965    }
966    if matches!(node.kind(), "identifier" | "template_function")
967        && call_for_function_node(node).is_some()
968    {
969        return;
970    }
971    if node.kind() == "using_declaration" {
972        let (resolution, type_node) =
973            if let Some(type_node) = using_enum_declaration_type_node(node) {
974                (
975                    resolve_using_enum_declaration_owner(
976                        node,
977                        &ctx.analyzer,
978                        ctx.visibility,
979                        &ctx.ordinary_type_imports,
980                        ctx.file,
981                        ctx.source,
982                    ),
983                    type_node,
984                )
985            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
986                (
987                    resolve_ordinary_using_declaration_owner(
988                        node,
989                        &ctx.analyzer,
990                        ctx.visibility,
991                        ctx.file,
992                        ctx.source,
993                    ),
994                    type_node,
995                )
996            } else {
997                return;
998            };
999        if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1000            && same_visible_symbol(&unit, &ctx.spec.target)
1001        {
1002            *ctx.raw_match_count += 1;
1003            push_type_hit(type_node, ctx);
1004        }
1005        return;
1006    }
1007    if is_c_sizeof_expression_type_candidate(ctx.file, node) {
1008        if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1009            || local_type_name_shadows(node, ctx)
1010        {
1011            return;
1012        }
1013        if let LexicalTypeResolution::Resolved {
1014            unit, candidates, ..
1015        } = resolve_type_node_lexically_for_target(
1016            node,
1017            &ctx.analyzer,
1018            ctx.visibility,
1019            &ctx.ordinary_type_imports,
1020            ctx.file,
1021            ctx.source,
1022            &ctx.spec.target,
1023            Some(&ctx.lexical_scope_cache),
1024            ctx.recovered_sentinel_scope(node).as_deref(),
1025        ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1026        {
1027            *ctx.raw_match_count += 1;
1028            push_type_hit(node, ctx);
1029        }
1030        return;
1031    }
1032    if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1033        // A missing `::` can put either the macro or the real type in the
1034        // recovered scope. Resolve both candidates against this inverse
1035        // target before choosing one: a unique match is routed through the
1036        // ordinary target-guided path, while two distinct matches are
1037        // ambiguous and must not invent a hit. With no target match, retain
1038        // the existing recovered-scope path below for its conservative
1039        // fallback behaviour.
1040        let mut matching = Vec::new();
1041        for candidate in [node, type_node] {
1042            if matching
1043                .iter()
1044                .any(|existing| same_node(*existing, candidate))
1045            {
1046                continue;
1047            }
1048            if let LexicalTypeResolution::Resolved {
1049                unit, candidates, ..
1050            } = resolve_type_node_lexically_for_target(
1051                candidate,
1052                &ctx.analyzer,
1053                ctx.visibility,
1054                &ctx.ordinary_type_imports,
1055                ctx.file,
1056                ctx.source,
1057                &ctx.spec.target,
1058                Some(&ctx.lexical_scope_cache),
1059                ctx.recovered_sentinel_scope(candidate).as_deref(),
1060            ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1061            {
1062                matching.push(candidate);
1063            }
1064        }
1065        match matching.as_slice() {
1066            [candidate] if !same_node(*candidate, node) => {
1067                maybe_record_type_hit(*candidate, ctx);
1068                return;
1069            }
1070            [candidate] if same_node(*candidate, node) => {}
1071            [] => {}
1072            _ => return,
1073        }
1074    }
1075    let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1076    let recovered_qualified_friend =
1077        is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1078    if !recovered_type
1079        && !matches!(
1080            node.kind(),
1081            "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1082        )
1083    {
1084        return;
1085    }
1086    if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1087        components.len() == 1 && !global && local_type_name_shadows(node, ctx)
1088    }) {
1089        return;
1090    }
1091    if !recovered_type
1092        && !recovered_qualified_friend
1093        && !recovered_exported_class_base
1094        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1095        && is_declaration_name(node)
1096        && let Some(owners) = out_of_line_member_definition_owner(
1097            &ctx.analyzer,
1098            ctx.visibility,
1099            ctx.file,
1100            ctx.source,
1101            node,
1102        )
1103    {
1104        *ctx.raw_match_count += 1;
1105        let mut matched_owner = false;
1106        for (owner_node, owner) in owners.owners {
1107            if same_visible_symbol(&owner, &ctx.spec.target) {
1108                matched_owner = true;
1109                push_guarded_owner_hit(owner_node, &owner, node, ctx);
1110            }
1111        }
1112        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1113            for scope in scopes {
1114                push_hit(scope, ctx);
1115            }
1116        } else if !matched_owner
1117            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1118        {
1119            push_unproven_hit(scope, ctx);
1120        }
1121        return;
1122    }
1123    if !recovered_type
1124        && !recovered_qualified_friend
1125        && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1126        && is_declaration_name(node)
1127        && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1128    {
1129        *ctx.raw_match_count += 1;
1130        push_type_hit(owner, ctx);
1131        return;
1132    }
1133    // A qualified template-id is represented as a qualified_identifier whose
1134    // name child is the template_type. Resolve the complete qualified
1135    // reference from that inner template node below; handling the outer node
1136    // independently would either lose the qualifier or emit a duplicate,
1137    // wider hit range.
1138    if ctx.visibility.is_template_specialization(&ctx.spec.target)
1139        && matches!(
1140            node.kind(),
1141            "qualified_identifier" | "scoped_type_identifier"
1142        )
1143        && node
1144            .child_by_field_name("name")
1145            .is_some_and(|name| name.kind() == "template_type")
1146    {
1147        return;
1148    }
1149    if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1150        *ctx.raw_match_count += 1;
1151        push_unproven_hit(scope, ctx);
1152        return;
1153    }
1154    if !recovered_type
1155        && !is_nested_type_node(node)
1156        && matches!(
1157            node.kind(),
1158            "qualified_identifier" | "scoped_type_identifier"
1159        )
1160        && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1161    {
1162        *ctx.raw_match_count += 1;
1163        for scope in scopes {
1164            push_type_hit(scope, ctx);
1165        }
1166        return;
1167    }
1168    if !recovered_type && is_nested_type_node(node) {
1169        if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1170            *ctx.raw_match_count += 1;
1171            push_type_hit(hit, ctx);
1172            return;
1173        }
1174        if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1175            *ctx.raw_match_count += 1;
1176            push_type_hit(hit, ctx);
1177            return;
1178        }
1179        // A concrete template specialization can be absent from the coarse
1180        // per-file component-name index: that index contains the primary name
1181        // while this recovered child may expose only the malformed template
1182        // leaf.  Resolve the complete enclosing template before applying the
1183        // component prefilter so an exact target candidate can prove this
1184        // reference.  Non-specialization targets retain the cheap gate.
1185        let nested_template = if node.kind() == "template_type" {
1186            Some(node)
1187        } else {
1188            node.parent().filter(|parent| {
1189                parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1190            })
1191        };
1192        // Concrete specializations need their nested template-id inspected.
1193        // An ordinary alias application does too when it is itself the scope
1194        // of a member-qualified type, because the enclosing node denotes the
1195        // member rather than the alias. In every other primary/alias shape the
1196        // enclosing structured type owns the hit range; descending would emit
1197        // a duplicate terminal subrange.
1198        let nested_alias_qualifier = nested_template.is_some_and(|template| {
1199            let enclosing_qualified_type_owns_range = template.parent().is_some_and(|parent| {
1200                matches!(
1201                    parent.kind(),
1202                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1203                ) && parent.child_by_field_name("name") == Some(template)
1204            });
1205            let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1206                return false;
1207            };
1208            let Some(name) = template_reference_name_node(template) else {
1209                return false;
1210            };
1211            let alias_candidates = ctx
1212                .visibility
1213                .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1214                .filter(|candidate| alias_provider.is_type_alias(candidate))
1215                .cloned()
1216                .collect::<Vec<_>>();
1217            let direct_target_alias_visible = !alias_candidates
1218                .iter()
1219                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1220                || ctx.visibility.external_type_candidate_visible_in_context(
1221                    &ctx.analyzer,
1222                    ctx.file,
1223                    &ctx.spec.target,
1224                    template,
1225                );
1226            !enclosing_qualified_type_owns_range
1227                && direct_target_alias_visible
1228                && template_reference_candidates_select_target(
1229                    template,
1230                    &alias_candidates,
1231                    &ctx.analyzer,
1232                    ctx.visibility,
1233                    ctx.file,
1234                    ctx.source,
1235                    &ctx.spec.target,
1236                )
1237        });
1238        if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1239            return;
1240        }
1241        if nested_alias_qualifier {
1242            let template = nested_template.expect("a nested alias qualifier has a template node");
1243            *ctx.raw_match_count += 1;
1244            let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1245                .unwrap_or_else(|| type_reference_hit_node(template));
1246            push_type_hit(hit, ctx);
1247            return;
1248        }
1249        // For concrete specializations, the visible identifier index can
1250        // already contain the exact template spelling even when lexical
1251        // resolution rejects the recovered child (the child itself has no
1252        // argument list).  That exact candidate is sufficient structured
1253        // evidence: retain the narrow template-name leaf and avoid emitting
1254        // the entire template-id range.
1255        if ctx.visibility.is_template_specialization(&ctx.spec.target)
1256            && let Some(template) = nested_template
1257            && template_type_component_preserves_target(
1258                template,
1259                &ctx.visibility
1260                    .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1261                    .cloned()
1262                    .collect::<Vec<_>>(),
1263                ctx,
1264            )
1265        {
1266            *ctx.raw_match_count += 1;
1267            let hit = template
1268                .child_by_field_name("name")
1269                .filter(|name| name.kind() == "type_identifier")
1270                .unwrap_or(template);
1271            push_type_hit(hit, ctx);
1272            return;
1273        }
1274        if let Some(template) = nested_template
1275            && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1276        {
1277            *ctx.raw_match_count += 1;
1278            let hit =
1279                target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1280                    if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1281                        template
1282                            .child_by_field_name("name")
1283                            .filter(|name| name.kind() == "type_identifier")
1284                            .unwrap_or(template)
1285                    } else {
1286                        type_reference_hit_node(template)
1287                    }
1288                });
1289            push_type_hit(hit, ctx);
1290        }
1291        return;
1292    }
1293    if !recovered_type && !type_reference_components_may_name_target(node, ctx) {
1294        return;
1295    }
1296    if !recovered_type && let Some(call) = call_for_function_node(node) {
1297        let direct_target = resolve_qualified_call_target(
1298            call,
1299            node,
1300            &ctx.analyzer,
1301            ctx.visibility,
1302            &ctx.ordinary_type_imports,
1303            ctx.file,
1304            ctx.source,
1305        );
1306        if matches!(direct_target, BareCallTargetResolution::Type(_))
1307            && let LexicalTypeResolution::Resolved {
1308                unit, candidates, ..
1309            } = resolve_type_node_lexically_for_target(
1310                node,
1311                &ctx.analyzer,
1312                ctx.visibility,
1313                &ctx.ordinary_type_imports,
1314                ctx.file,
1315                ctx.source,
1316                &ctx.spec.target,
1317                Some(&ctx.lexical_scope_cache),
1318                ctx.recovered_sentinel_scope(node).as_deref(),
1319            )
1320            && type_resolution_matches_target(node, &unit, &candidates, ctx)
1321        {
1322            *ctx.raw_match_count += 1;
1323            push_type_hit(type_reference_hit_node(node), ctx);
1324        } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1325            *ctx.raw_match_count += 1;
1326            for scope in scopes {
1327                push_type_hit(scope, ctx);
1328            }
1329        } else if let Some(scope) = target_guided_unproven_qualified_value_owner_scope(node, ctx) {
1330            *ctx.raw_match_count += 1;
1331            push_unproven_hit(scope, ctx);
1332        }
1333        return;
1334    }
1335    if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1336        *ctx.raw_match_count += 1;
1337        if proven {
1338            push_type_hit(hit, ctx);
1339        } else {
1340            push_unproven_hit(hit, ctx);
1341        }
1342        return;
1343    }
1344    if !recovered_type
1345        && !recovered_qualified_friend
1346        && !recovered_exported_class_base
1347        && is_declaration_name(node)
1348    {
1349        let mut matched_owner = false;
1350        if let Some(owners) = out_of_line_member_definition_owner(
1351            &ctx.analyzer,
1352            ctx.visibility,
1353            ctx.file,
1354            ctx.source,
1355            node,
1356        ) {
1357            for (owner_node, owner) in owners.owners {
1358                if same_visible_symbol(&owner, &ctx.spec.target) {
1359                    matched_owner = true;
1360                    *ctx.raw_match_count += 1;
1361                    push_guarded_owner_hit(owner_node, &owner, node, ctx);
1362                }
1363            }
1364        }
1365        if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1366            *ctx.raw_match_count += 1;
1367            for scope in scopes {
1368                push_hit(scope, ctx);
1369            }
1370        } else if !matched_owner
1371            && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1372        {
1373            *ctx.raw_match_count += 1;
1374            push_unproven_hit(scope, ctx);
1375        }
1376        return;
1377    }
1378    let hit_node = node;
1379    let text = node_text(hit_node, ctx.source);
1380    let type_resolution = if hit_node.kind() == "template_type"
1381        && ctx.visibility.is_template_specialization(&ctx.spec.target)
1382    {
1383        resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1384            resolve_type_node_lexically_for_target(
1385                hit_node,
1386                &ctx.analyzer,
1387                ctx.visibility,
1388                &ctx.ordinary_type_imports,
1389                ctx.file,
1390                ctx.source,
1391                &ctx.spec.target,
1392                Some(&ctx.lexical_scope_cache),
1393                ctx.recovered_sentinel_scope(hit_node).as_deref(),
1394            )
1395        })
1396    } else {
1397        resolve_type_node_lexically_for_target(
1398            hit_node,
1399            &ctx.analyzer,
1400            ctx.visibility,
1401            &ctx.ordinary_type_imports,
1402            ctx.file,
1403            ctx.source,
1404            &ctx.spec.target,
1405            Some(&ctx.lexical_scope_cache),
1406            ctx.recovered_sentinel_scope(hit_node).as_deref(),
1407        )
1408    };
1409    let type_resolution = if matches!(type_resolution, LexicalTypeResolution::Missing) {
1410        orphaned_namespace_alias_type_resolution(hit_node, ctx).unwrap_or(type_resolution)
1411    } else {
1412        type_resolution
1413    };
1414    match type_resolution {
1415        LexicalTypeResolution::Resolved {
1416            unit, candidates, ..
1417        } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1418            *ctx.raw_match_count += 1;
1419            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1420                for scope in scopes {
1421                    push_type_hit(scope, ctx);
1422                }
1423            } else {
1424                let hit_node = if recovered_type && hit_node.kind() == "template_type" {
1425                    hit_node
1426                        .child_by_field_name("name")
1427                        .filter(|name| name.kind() == "type_identifier")
1428                        .unwrap_or(hit_node)
1429                } else if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1430                    hit_node
1431                        .child_by_field_name("name")
1432                        .filter(|name| name.kind() == "type_identifier")
1433                        .unwrap_or(hit_node)
1434                } else if ctx
1435                    .analyzer
1436                    .type_alias_provider()
1437                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1438                    && ctx
1439                        .analyzer
1440                        .parent_of(&ctx.spec.target)
1441                        .is_some_and(|owner| owner.is_class())
1442                    && ctx
1443                        .visibility
1444                        .is_exhaustive_same_fqn_type_declaration_family(
1445                            &ctx.analyzer,
1446                            ctx.file,
1447                            &ctx.spec.target,
1448                        )
1449                    && matches!(
1450                        hit_node.kind(),
1451                        "qualified_identifier" | "scoped_type_identifier"
1452                    )
1453                {
1454                    hit_node.child_by_field_name("name").unwrap_or(hit_node)
1455                } else if cpp_template_reference_arguments(hit_node, ctx.source).is_some()
1456                    && ctx.analyzer.type_alias_provider().is_some_and(|provider| {
1457                        candidates.iter().any(|candidate| {
1458                            provider.is_type_alias(candidate)
1459                                && ctx
1460                                    .visibility
1461                                    .is_exhaustive_same_fqn_type_declaration_family(
1462                                        &ctx.analyzer,
1463                                        ctx.file,
1464                                        candidate,
1465                                    )
1466                        })
1467                    })
1468                {
1469                    let terminal = template_reference_name_node(hit_node)
1470                        .map(function_terminal_node)
1471                        .unwrap_or(hit_node);
1472                    push_type_hit(hit_node, ctx);
1473                    if terminal.start_byte() != hit_node.start_byte()
1474                        || terminal.end_byte() != hit_node.end_byte()
1475                    {
1476                        push_type_hit(terminal, ctx);
1477                    }
1478                    return;
1479                } else if qualified_type_scope_contains_template(hit_node) {
1480                    function_terminal_node(hit_node)
1481                } else {
1482                    hit_node
1483                };
1484                let hit_node = target_guided_missing_orphaned_namespace_type_leaf(hit_node, ctx)
1485                    .unwrap_or(hit_node);
1486                let hit_node = type_reference_hit_node(hit_node);
1487                let qualified_alias = qualified_alias_reference_preserves_target(
1488                    node,
1489                    &ctx.spec.target,
1490                    &ctx.analyzer,
1491                    ctx.visibility,
1492                    ctx.file,
1493                    ctx.source,
1494                );
1495                push_type_hit(hit_node, ctx);
1496                if qualified_alias_reference_requires_terminal(qualified_alias)
1497                    || initialized_type_declaration_with_cast(node)
1498                {
1499                    let terminal = function_terminal_node(hit_node);
1500                    if terminal.start_byte() != hit_node.start_byte()
1501                        || terminal.end_byte() != hit_node.end_byte()
1502                    {
1503                        push_type_hit(terminal, ctx);
1504                    }
1505                }
1506            }
1507            return;
1508        }
1509        LexicalTypeResolution::Resolved {
1510            unit: _,
1511            candidates,
1512            ..
1513        } => {
1514            if let Some(leaf) = target_guided_missing_orphaned_namespace_type_leaf(node, ctx) {
1515                *ctx.raw_match_count += 1;
1516                push_type_hit(leaf, ctx);
1517            } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1518                *ctx.raw_match_count += 1;
1519                for scope in scopes {
1520                    push_type_hit(scope, ctx);
1521                }
1522            } else if let Some(hit) =
1523                target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1524            {
1525                *ctx.raw_match_count += 1;
1526                push_unproven_hit(hit, ctx);
1527            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1528                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1529            {
1530                *ctx.raw_match_count += 1;
1531                push_type_hit(leaf, ctx);
1532            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1533                *ctx.raw_match_count += 1;
1534                push_unproven_hit(leaf, ctx);
1535            }
1536            return;
1537        }
1538        LexicalTypeResolution::Ambiguous => {
1539            if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1540                *ctx.raw_match_count += 1;
1541                for scope in scopes {
1542                    push_type_hit(scope, ctx);
1543                }
1544            } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1545                *ctx.raw_match_count += 1;
1546                push_unproven_hit(leaf, ctx);
1547            } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1548                .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1549                .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1550            {
1551                *ctx.raw_match_count += 1;
1552                push_type_hit(leaf, ctx);
1553            }
1554            return;
1555        }
1556        LexicalTypeResolution::Missing => {
1557            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1558                &ctx.analyzer,
1559                ctx.file,
1560                hit_node,
1561                ctx.source,
1562            ) && same_visible_symbol(&unit, &ctx.spec.target)
1563            {
1564                *ctx.raw_match_count += 1;
1565                push_type_hit(hit_node, ctx);
1566                return;
1567            }
1568            if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1569                *ctx.raw_match_count += 1;
1570                push_unproven_hit(leaf, ctx);
1571                return;
1572            }
1573            if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1574                *ctx.raw_match_count += 1;
1575                push_unproven_hit(leaf, ctx);
1576                return;
1577            }
1578            if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1579                *ctx.raw_match_count += 1;
1580                push_type_hit(leaf, ctx);
1581                return;
1582            }
1583            let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1584                hit_node,
1585                &ctx.analyzer,
1586                ctx.visibility,
1587                ctx.file,
1588                ctx.source,
1589                &ctx.spec.target,
1590            );
1591            let raw_matches = matches!(
1592                raw_resolution,
1593                LexicalTypeResolution::Resolved {
1594                    ref unit,
1595                    ref candidates,
1596                    ..
1597                } if type_resolution_identifies_unit_target(
1598                    hit_node,
1599                    unit,
1600                    candidates,
1601                    &ctx.spec.target,
1602                    ctx,
1603                )
1604            );
1605            if raw_matches
1606                || type_node_has_exact_target_identity_without_visibility(
1607                    hit_node,
1608                    &ctx.analyzer,
1609                    ctx.visibility,
1610                    ctx.file,
1611                    ctx.source,
1612                    &ctx.spec.target,
1613                )
1614            {
1615                *ctx.raw_match_count += 1;
1616                push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1617                return;
1618            }
1619        }
1620    }
1621    // A class-owned alias can be qualified by an owner made visible through a
1622    // namespace import, including an import in an earlier reopening of the
1623    // same unnamed namespace. Resolve that owner before requiring an
1624    // enclosing class for inherited nested-type lookup.
1625    if ctx
1626        .visibility
1627        .parser_alias_resolves_to_type(&ctx.analyzer, ctx.file, text, &ctx.spec.target)
1628    {
1629        *ctx.raw_match_count += 1;
1630        push_type_hit(type_reference_hit_node(hit_node), ctx);
1631        return;
1632    }
1633    if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1634        *ctx.raw_match_count += 1;
1635        for scope in scopes {
1636            push_type_hit(scope, ctx);
1637        }
1638        return;
1639    }
1640    if !name_mentions(text, &ctx.spec.member_name) {
1641        return;
1642    }
1643    *ctx.raw_match_count += 1;
1644    if !ctx.visibility.external_type_candidate_visible_in_context(
1645        &ctx.analyzer,
1646        ctx.file,
1647        &ctx.spec.target,
1648        hit_node,
1649    ) {
1650        if let Some(scope) = static_qualifier_name_scope(node, ctx) {
1651            push_unproven_hit(scope, ctx);
1652        } else {
1653            push_unproven_hit(hit_node, ctx);
1654        }
1655    }
1656}
1657
1658fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1659    for reference in object_macro_replacement_type_references(node, ctx.source) {
1660        for component_count in 1..=reference.components.len() {
1661            let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1662                node,
1663                &reference.components[..component_count],
1664                reference.global,
1665                &ctx.analyzer,
1666                ctx.visibility,
1667                &ctx.ordinary_type_imports,
1668                ctx.file,
1669                ctx.source,
1670                &ctx.spec.target,
1671                false,
1672                Some(&ctx.lexical_scope_cache),
1673            );
1674            let matches_target = match resolution {
1675                LexicalTypeResolution::Resolved {
1676                    unit, candidates, ..
1677                } => {
1678                    same_visible_symbol(&unit, &ctx.spec.target)
1679                        || candidates
1680                            .iter()
1681                            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1682                }
1683                LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1684                    &ctx.analyzer,
1685                    ctx.visibility,
1686                    ctx.file,
1687                    &reference.components[..component_count],
1688                )
1689                .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1690                LexicalTypeResolution::Ambiguous => false,
1691            };
1692            if !matches_target {
1693                continue;
1694            }
1695            let range = &reference.component_ranges[component_count - 1];
1696            *ctx.raw_match_count += 1;
1697            push_type_hit_range(node, range.start, range.end, ctx);
1698        }
1699    }
1700}
1701
1702fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1703    node: Node<'tree>,
1704    ctx: &ScanCtx<'_>,
1705) -> Option<Node<'tree>> {
1706    let (components, global) = type_reference_components(node, ctx.source)?;
1707    let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1708        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1709            node,
1710            &ctx.analyzer,
1711            ctx.visibility,
1712            ctx.file,
1713            ctx.source,
1714            false,
1715            false,
1716            Some(&ctx.lexical_scope_cache),
1717        ) {
1718            LexicalScopeResolution::Resolved(scope) => Some(scope),
1719            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1720        }
1721    })?;
1722    let OrdinaryTypeImportResolution::Resolved { target, .. } =
1723        compatible_foreign_type_import_resolution(
1724            node,
1725            &components,
1726            global,
1727            &ctx.analyzer,
1728            ctx.visibility,
1729            &ctx.ordinary_type_imports,
1730            ctx.file,
1731            ctx.source,
1732            &lexical_scope,
1733            Some(&ctx.spec.target),
1734        )
1735    else {
1736        return None;
1737    };
1738    same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
1739}
1740
1741/// Resolve the terminal of `Owner::Nested` when complete type lookup cannot
1742/// express the owner as its first qualified component. This occurs when a
1743/// using-directive imports `Owner` and when inherited-type lookup injects it.
1744/// Resolve the owner prefix independently, then retain the terminal after that
1745/// owner proves the target's direct nested declaration.
1746fn target_guided_nested_type_terminal_hit<'tree>(
1747    node: Node<'tree>,
1748    ctx: &ScanCtx<'_>,
1749) -> Option<Node<'tree>> {
1750    let qualified = node.parent().filter(|parent| {
1751        matches!(
1752            parent.kind(),
1753            "qualified_identifier" | "scoped_type_identifier"
1754        ) && parent.child_by_field_name("name") == Some(node)
1755    })?;
1756    let mut complete = qualified;
1757    while let Some(parent) = complete.parent().filter(|parent| {
1758        matches!(
1759            parent.kind(),
1760            "qualified_identifier" | "scoped_type_identifier"
1761        )
1762    }) {
1763        complete = parent;
1764    }
1765    let owner = qualified_owner_components(complete, ctx.source)?;
1766
1767    if let LexicalTypeResolution::Resolved {
1768        unit, candidates, ..
1769    } = resolve_type_node_lexically_for_target(
1770        complete,
1771        &ctx.analyzer,
1772        ctx.visibility,
1773        &ctx.ordinary_type_imports,
1774        ctx.file,
1775        ctx.source,
1776        &ctx.spec.target,
1777        Some(&ctx.lexical_scope_cache),
1778        ctx.recovered_sentinel_scope(complete).as_deref(),
1779    ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
1780        && ctx
1781            .analyzer
1782            .parent_of(&ctx.spec.target)
1783            .is_some_and(|parent| parent.is_class())
1784        && !ctx
1785            .analyzer
1786            .type_alias_provider()
1787            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1788        && is_namespace_scope_type_reference(complete)
1789        && namespace_import_can_name_nested_target(complete, ctx)
1790    {
1791        return Some(node);
1792    }
1793
1794    let target_owner = ctx
1795        .analyzer
1796        .parent_of(&ctx.spec.target)
1797        .filter(CodeUnit::is_class);
1798    let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
1799        let target_components = canonical_cpp_scope_components(target_owner);
1800        if owner.global {
1801            target_components == owner.names
1802        } else {
1803            target_components.ends_with(&owner.names)
1804        }
1805    });
1806    let target_member_visible = |target_owner: &CodeUnit| {
1807        ctx.visibility
1808            .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
1809            .into_iter()
1810            .any(|candidate| {
1811                same_visible_symbol(candidate, &ctx.spec.target)
1812                    && (ctx
1813                        .visibility
1814                        .external_type_candidate_guard_compatible_in_context(
1815                            &ctx.analyzer,
1816                            ctx.file,
1817                            candidate,
1818                            complete,
1819                        )
1820                        || (ctx
1821                            .visibility
1822                            .is_exhaustive_same_fqn_type_declaration_family(
1823                                &ctx.analyzer,
1824                                ctx.file,
1825                                candidate,
1826                            )
1827                            && ctx.visibility.external_type_candidate_visible_in_context(
1828                                &ctx.analyzer,
1829                                ctx.file,
1830                                candidate,
1831                                complete,
1832                            )))
1833            })
1834    };
1835
1836    if owner_spelling_can_name_target
1837        && (is_namespace_scope_type_reference(complete)
1838            || (is_compound_type_reference(complete)
1839                && namespace_import_can_name_nested_target(complete, ctx)))
1840        && let Some(target_owner) = target_owner.as_ref()
1841        && let LexicalTypeResolution::Resolved { unit, .. } =
1842            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
1843                complete,
1844                &owner.names,
1845                owner.global,
1846                &ctx.analyzer,
1847                ctx.visibility,
1848                &ctx.ordinary_type_imports,
1849                ctx.file,
1850                ctx.source,
1851                Some(&ctx.lexical_scope_cache),
1852            )
1853        && ctx
1854            .visibility
1855            .same_template_owner_identity(&unit, target_owner)
1856        && target_member_visible(target_owner)
1857    {
1858        return Some(node);
1859    }
1860
1861    if ctx
1862        .analyzer
1863        .type_alias_provider()
1864        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1865        && !ctx
1866            .analyzer
1867            .parent_of(&ctx.spec.target)
1868            .is_some_and(|owner| owner.is_class())
1869        && let Some(target_owner) = target_owner.as_ref()
1870        && let LexicalTypeResolution::Resolved {
1871            unit, candidates, ..
1872        } = resolve_type_components_lexically_at_for_target_with_scope_cache(
1873            complete,
1874            &owner.names,
1875            owner.global,
1876            &ctx.analyzer,
1877            ctx.visibility,
1878            &ctx.ordinary_type_imports,
1879            ctx.file,
1880            ctx.source,
1881            target_owner,
1882            false,
1883            Some(&ctx.lexical_scope_cache),
1884        )
1885        && (ctx
1886            .visibility
1887            .same_template_owner_identity(&unit, target_owner)
1888            || candidates.iter().any(|candidate| {
1889                ctx.visibility
1890                    .same_template_owner_identity(candidate, target_owner)
1891            }))
1892        && target_member_visible(target_owner)
1893    {
1894        return Some(node);
1895    }
1896
1897    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
1898    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
1899    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
1900        &ctx.analyzer,
1901        ctx.file,
1902        &owner.names,
1903        owner.global,
1904        &lexical_scope,
1905    );
1906    let owner_unit = match owner_resolution {
1907        LexicalTypeResolution::Resolved { unit, .. } => unit,
1908        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
1909            ctx.visibility.inherited_injected_class_owner(
1910                &ctx.analyzer,
1911                ctx.file,
1912                &enclosing_owner,
1913                owner.names.first()?,
1914            )?
1915        }
1916        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
1917    };
1918    let name = node_text(node, ctx.source);
1919    ctx.visibility
1920        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
1921        .into_iter()
1922        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1923        .then_some(complete)
1924}
1925
1926fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
1927    // Namespace-scope nested classes need a terminal edge when the complete
1928    // qualified node is the only structured evidence (as in #2213). In
1929    // parameters, fields, and callable bodies the enclosing type node already
1930    // owns the exact range; adding its terminal would duplicate that edge.
1931    let mut current = node.parent();
1932    while let Some(parent) = current {
1933        match parent.kind() {
1934            "call_expression" | "new_expression" => return true,
1935            "parameter_declaration"
1936            | "optional_parameter_declaration"
1937            | "field_declaration"
1938            | "function_definition"
1939            | "function_declarator"
1940            | "class_specifier"
1941            | "struct_specifier"
1942            | "union_specifier"
1943            | "compound_statement" => return false,
1944            "declaration" => {
1945                let mut cursor = parent.walk();
1946                if parent
1947                    .named_children(&mut cursor)
1948                    .any(|child| child.kind() == "function_declarator")
1949                {
1950                    return false;
1951                }
1952            }
1953            _ => {}
1954        }
1955        current = parent.parent();
1956    }
1957    true
1958}
1959
1960fn is_compound_type_reference(node: Node<'_>) -> bool {
1961    let mut current = node.parent();
1962    while let Some(parent) = current {
1963        match parent.kind() {
1964            "compound_statement" => return true,
1965            "namespace_definition" | "translation_unit" => return false,
1966            _ => current = parent.parent(),
1967        }
1968    }
1969    false
1970}
1971
1972fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
1973    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
1974        return false;
1975    };
1976    effective_using_bindings_for_name(
1977        ctx.visibility,
1978        &ctx.ordinary_type_imports,
1979        ctx.file,
1980        node,
1981        ctx.source,
1982        target_owner.identifier(),
1983    )
1984    .iter()
1985    .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
1986}
1987
1988/// Recover the class-template qualifier in a dependent leading return type of
1989/// an out-of-line member definition.
1990///
1991/// `typename Owner<T>::Alias Owner<T>::method()` contains two references to
1992/// `Owner`: one in the return type and one in the callable declarator. The
1993/// latter already has authoritative indexed owner resolution. Reuse that
1994/// structured owner evidence for the former when both template names agree;
1995/// resolving `Owner<T>::Alias` as one type would instead select terminal
1996/// `Alias` and discard the qualifier.
1997fn out_of_line_dependent_return_template_owner<'tree>(
1998    node: Node<'tree>,
1999    ctx: &ScanCtx<'_>,
2000) -> Option<Node<'tree>> {
2001    if node.kind() != "template_type" {
2002        return None;
2003    }
2004    let template_name = template_reference_name_node(node)?;
2005    let mut scope = node;
2006    while let Some(parent) = scope.parent().filter(|parent| {
2007        matches!(
2008            parent.kind(),
2009            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2010        ) && parent.child_by_field_name("name") == Some(scope)
2011    }) {
2012        scope = parent;
2013    }
2014    let qualified = scope.parent().filter(|parent| {
2015        matches!(
2016            parent.kind(),
2017            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2018        ) && parent.child_by_field_name("scope") == Some(scope)
2019    })?;
2020    let mut function = qualified.parent();
2021    let function = loop {
2022        let candidate = function?;
2023        if candidate.kind() == "function_definition" {
2024            break candidate;
2025        }
2026        function = candidate.parent();
2027    };
2028    let return_type = function.child_by_field_name("type")?;
2029    if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2030        return None;
2031    }
2032    let declarator_name = function
2033        .child_by_field_name("declarator")
2034        .and_then(declarator_name_node)?;
2035    if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2036        return None;
2037    }
2038    let resolved_owner_matches = out_of_line_member_definition_owner(
2039        &ctx.analyzer,
2040        ctx.visibility,
2041        ctx.file,
2042        ctx.source,
2043        declarator_name,
2044    )
2045    .is_some_and(|owners| {
2046        owners
2047            .owners
2048            .iter()
2049            .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2050    });
2051    let indexed_owner_matches =
2052        indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2053    let target_guided_owner_matches =
2054        target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2055    (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2056        .then_some(template_name)
2057}
2058
2059/// Use the indexed callable identity when a malformed namespace sentinel
2060/// prevents lexical lookup of an out-of-line template owner.
2061fn indexed_out_of_line_template_owner_hit<'tree>(
2062    node: Node<'tree>,
2063    ctx: &ScanCtx<'_>,
2064) -> Option<Node<'tree>> {
2065    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2066        || !is_declaration_name(node)
2067    {
2068        return None;
2069    }
2070    let mut function = node.parent();
2071    let function = loop {
2072        let candidate = function?;
2073        if candidate.kind() == "function_definition" {
2074            break candidate;
2075        }
2076        function = candidate.parent();
2077    };
2078    if function
2079        .child_by_field_name("declarator")
2080        .and_then(declarator_name_node)
2081        != Some(node)
2082    {
2083        return None;
2084    }
2085    let target = physically_visible_type_target(ctx)?;
2086    let qualified = qualified_owner_components(node, ctx.source)?;
2087    if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2088        return None;
2089    }
2090    if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2091        != canonical_cpp_scope_components(target)
2092    {
2093        return None;
2094    }
2095    let owner = qualified.nodes.last().copied()?;
2096    let template = if owner.kind() == "template_type" {
2097        owner
2098    } else {
2099        owner
2100            .parent()
2101            .filter(|parent| parent.kind() == "template_type")?
2102    };
2103    template_reference_name_node(template)
2104}
2105
2106fn push_guarded_owner_hit(
2107    owner_node: Node<'_>,
2108    owner: &CodeUnit,
2109    reference: Node<'_>,
2110    ctx: &mut ScanCtx<'_>,
2111) {
2112    if ctx
2113        .visibility
2114        .external_type_candidate_guard_compatible_in_context(
2115            &ctx.analyzer,
2116            ctx.file,
2117            owner,
2118            reference,
2119        )
2120    {
2121        push_hit(owner_node, ctx);
2122    } else {
2123        push_unproven_hit(owner_node, ctx);
2124    }
2125}
2126
2127/// Preserve a direct template-alias reference when a macro namespace sentinel
2128/// makes tree-sitter drop the first source path component.
2129fn target_guided_alias_template_reference<'tree>(
2130    node: Node<'tree>,
2131    ctx: &ScanCtx<'_>,
2132) -> Option<(Node<'tree>, bool)> {
2133    let alias_provider = ctx.analyzer.type_alias_provider()?;
2134    if !matches!(
2135        node.kind(),
2136        "qualified_identifier" | "scoped_type_identifier"
2137    ) || !alias_provider.is_type_alias(&ctx.spec.target)
2138    {
2139        return None;
2140    }
2141    cpp_template_reference_arguments(node, ctx.source)?;
2142    let (components, global) = type_reference_components(node, ctx.source)?;
2143    if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2144        return None;
2145    }
2146    let target = physically_visible_type_target(ctx)?;
2147    let target_components = canonical_cpp_scope_components(target);
2148    let parser_namespace = enclosing_namespace_components(node, ctx.source);
2149    let path_matches = if global {
2150        components == target_components
2151            || (!parser_namespace.is_empty()
2152                && target_components.starts_with(&parser_namespace)
2153                && target_components[parser_namespace.len()..] == components)
2154    } else {
2155        let lexical_scope = match enclosing_lexical_scope_components(
2156            node,
2157            &ctx.analyzer,
2158            ctx.visibility,
2159            ctx.file,
2160            ctx.source,
2161        ) {
2162            LexicalScopeResolution::Resolved(scope) => scope,
2163            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2164        };
2165        lexical_component_tiers(&components, false, &lexical_scope)
2166            .any(|scope| scope == target_components)
2167    };
2168    let scoped_candidates = ctx
2169        .visibility
2170        .visible_identifier_candidates(ctx.file, target.identifier())
2171        .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2172        .collect::<Vec<_>>();
2173    if !path_matches
2174        || scoped_candidates.is_empty()
2175        || scoped_candidates
2176            .iter()
2177            .any(|candidate| !same_visible_symbol(candidate, target))
2178        || !ctx.visibility.structured_alias_primary_preserves_target(
2179            &ctx.analyzer,
2180            ctx.file,
2181            target,
2182            target,
2183        )
2184    {
2185        return None;
2186    }
2187    let proven = ctx.visibility.external_type_candidate_visible_in_context(
2188        &ctx.analyzer,
2189        ctx.file,
2190        target,
2191        node,
2192    );
2193    Some((node, proven))
2194}
2195
2196/// Tree-sitter can split a macro-qualified member return type into a phantom
2197/// field followed by the real function definition.  The declaration visitor
2198/// discards that phantom field, but its declarator token remains a semantic
2199/// type reference. Resolve it from the recovered or indexed class scope so an
2200/// enclosing-class alias remains distinguishable from same-spelled siblings.
2201fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2202    let name = node_text(return_type, ctx.source);
2203    if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2204        return;
2205    }
2206    if physically_visible_type_target(ctx).is_some()
2207        && type_alias_owner_encloses_structured_reference(return_type, ctx)
2208        && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2209        && ctx.visibility.external_type_candidate_visible_in_context(
2210            &ctx.analyzer,
2211            ctx.file,
2212            &ctx.spec.target,
2213            return_type,
2214        )
2215    {
2216        *ctx.raw_match_count += 1;
2217        push_type_hit(return_type, ctx);
2218        return;
2219    }
2220    let Some(scope) = ctx
2221        .recovered_sentinel_scope(return_type)
2222        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2223    else {
2224        return;
2225    };
2226    let components = [name.to_string()];
2227    let resolution = ctx.visibility.resolve_type_components_lexically(
2228        &ctx.analyzer,
2229        ctx.file,
2230        &components,
2231        false,
2232        &scope,
2233    );
2234    if let LexicalTypeResolution::Resolved {
2235        unit, candidates, ..
2236    } = resolution
2237        && (same_visible_symbol(&unit, &ctx.spec.target)
2238            || candidates
2239                .iter()
2240                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2241    {
2242        *ctx.raw_match_count += 1;
2243        push_type_hit(return_type, ctx);
2244    }
2245}
2246
2247/// Return the class/struct scope in a C++ pointer-to-member declarator such as
2248/// `double Owner::*member`. Tree-sitter represents the owner as the `scope` of
2249/// a `qualified_identifier`, while the `name` is a pointer declarator rather
2250/// than a type node; ordinary type-reference traversal therefore skips it.
2251fn member_pointer_owner_components<'tree>(
2252    node: Node<'tree>,
2253    source: &str,
2254) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2255    if node.kind() != "qualified_identifier" {
2256        return None;
2257    }
2258    let declarator = node.child_by_field_name("name")?;
2259    if !matches!(
2260        declarator.kind(),
2261        "pointer_type_declarator" | "abstract_pointer_declarator"
2262    ) {
2263        return None;
2264    }
2265    let scope = node.child_by_field_name("scope")?;
2266    // Keep this check entirely structural.  C and C++ share the
2267    // `qualified_identifier` node shape for some recovered declarations, but
2268    // only the C++ member-pointer form has an actual `::` grammar child
2269    // between the owner scope and pointer declarator.  Looking at the source
2270    // slice here would make a recovered C declarator look like a C++ owner
2271    // merely because its bytes happen to contain the same punctuation.
2272    let mut saw_scope = false;
2273    let has_scope_separator = (0..node.child_count()).any(|index| {
2274        let Some(child) = node.child(index) else {
2275            return false;
2276        };
2277        if same_node(child, scope) {
2278            saw_scope = true;
2279            return false;
2280        }
2281        saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2282    });
2283    if !has_scope_separator {
2284        return None;
2285    }
2286    let mut nodes = cpp_name_component_nodes(scope)?;
2287    let mut outer = node;
2288    while let Some(parent) = outer.parent()
2289        && parent.kind() == "qualified_identifier"
2290        && parent.child_by_field_name("name") == Some(outer)
2291    {
2292        let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2293        prefix.append(&mut nodes);
2294        nodes = prefix;
2295        outer = parent;
2296    }
2297    let names = nodes
2298        .iter()
2299        .map(|component| node_text(*component, source).to_string())
2300        .collect();
2301    Some((
2302        QualifiedOwnerComponents {
2303            nodes,
2304            names,
2305            global: is_globally_qualified_cpp_name(outer),
2306        },
2307        outer,
2308    ))
2309}
2310
2311fn member_pointer_alias_owner_prefix_matches(
2312    node: Node<'_>,
2313    owner: &QualifiedOwnerComponents<'_>,
2314    ctx: &ScanCtx<'_>,
2315) -> bool {
2316    let Some((_, owner_prefix)) = owner.names.split_last() else {
2317        return false;
2318    };
2319    let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2320        return false;
2321    };
2322    let recovered_scope = ctx.recovered_sentinel_scope(node);
2323    let resolution = if let Some(recovered_scope) = recovered_scope {
2324        resolve_type_components_lexically_at_for_target_with_recovered_scope(
2325            node,
2326            owner_prefix,
2327            owner.global,
2328            &ctx.analyzer,
2329            ctx.visibility,
2330            &ctx.ordinary_type_imports,
2331            ctx.file,
2332            ctx.source,
2333            &parent,
2334            false,
2335            &recovered_scope,
2336        )
2337    } else {
2338        resolve_type_components_lexically_at_for_target_with_scope_cache(
2339            node,
2340            owner_prefix,
2341            owner.global,
2342            &ctx.analyzer,
2343            ctx.visibility,
2344            &ctx.ordinary_type_imports,
2345            ctx.file,
2346            ctx.source,
2347            &parent,
2348            false,
2349            Some(&ctx.lexical_scope_cache),
2350        )
2351    };
2352    let LexicalTypeResolution::Resolved {
2353        unit, candidates, ..
2354    } = resolution
2355    else {
2356        return false;
2357    };
2358    same_member_pointer_owner_identity(&unit, &parent)
2359        || candidates
2360            .iter()
2361            .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2362}
2363
2364fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2365    same_visible_symbol(left, right)
2366        || (left.kind() == right.kind()
2367            && left.fq_name() == right.fq_name()
2368            && left.source() == right.source())
2369}
2370
2371/// Resolve a template type nested in a qualified identifier against a concrete
2372/// type target. The target-guided lexical path intentionally applies a
2373/// structured candidate prefilter; that prefilter cannot see a partial
2374/// specialization until the template arguments have selected it. Resolve the
2375/// complete qualified primary first, then apply the parsed arguments and
2376/// retain the result only when it is the requested target.
2377fn resolve_nested_template_type_for_target(
2378    node: Node<'_>,
2379    ctx: &ScanCtx<'_>,
2380) -> Option<LexicalTypeResolution> {
2381    let reference_node = node
2382        .parent()
2383        .filter(|parent| {
2384            parent.kind() == "qualified_identifier"
2385                && parent.child_by_field_name("name") == Some(node)
2386        })
2387        .unwrap_or(node);
2388    let target_resolution = resolve_type_node_lexically_for_target(
2389        reference_node,
2390        &ctx.analyzer,
2391        ctx.visibility,
2392        &ctx.ordinary_type_imports,
2393        ctx.file,
2394        ctx.source,
2395        &ctx.spec.target,
2396        Some(&ctx.lexical_scope_cache),
2397        ctx.recovered_sentinel_scope(reference_node).as_deref(),
2398    );
2399    if let LexicalTypeResolution::Resolved {
2400        unit, candidates, ..
2401    } = target_resolution
2402        && template_reference_candidates_select_target(
2403            reference_node,
2404            &candidates,
2405            &ctx.analyzer,
2406            ctx.visibility,
2407            ctx.file,
2408            ctx.source,
2409            &ctx.spec.target,
2410        )
2411    {
2412        return Some(LexicalTypeResolution::Resolved {
2413            unit,
2414            components: Vec::new(),
2415            candidates,
2416        });
2417    }
2418
2419    let normal_resolution = resolve_type_node_lexically(
2420        reference_node,
2421        &ctx.analyzer,
2422        ctx.visibility,
2423        &ctx.ordinary_type_imports,
2424        ctx.file,
2425        ctx.source,
2426    );
2427    let LexicalTypeResolution::Resolved {
2428        unit,
2429        components,
2430        candidates,
2431    } = normal_resolution
2432    else {
2433        return None;
2434    };
2435    let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2436    let specialized = ctx
2437        .visibility
2438        .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2439        .ok()
2440        .unwrap_or(unit);
2441    (same_visible_symbol(&specialized, &ctx.spec.target)
2442        || template_type_component_preserves_target(reference_node, &candidates, ctx))
2443    .then_some(LexicalTypeResolution::Resolved {
2444        unit: specialized,
2445        components,
2446        candidates,
2447    })
2448}
2449
2450fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2451    let Some((components, _)) = type_reference_components(node, ctx.source) else {
2452        return false;
2453    };
2454    components.iter().any(|component| {
2455        ctx.type_reference_component_names.contains(component)
2456            || ctx.visibility.parser_alias_name_may_resolve_to_target(
2457                ctx.file,
2458                component,
2459                &ctx.spec.target,
2460            )
2461            || (component == ctx.spec.target.identifier()
2462                && matches!(
2463                    node.kind(),
2464                    "qualified_identifier" | "scoped_type_identifier"
2465                )
2466                && cpp_template_reference_arguments(node, ctx.source).is_some()
2467                && ctx
2468                    .analyzer
2469                    .type_alias_provider()
2470                    .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2471                && physically_visible_type_target(ctx).is_some())
2472    })
2473}
2474
2475fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2476    let Some(scope) = node.child_by_field_name("scope") else {
2477        return false;
2478    };
2479    let mut pending = vec![scope];
2480    while let Some(candidate) = pending.pop() {
2481        if candidate.kind() == "template_type" {
2482            return true;
2483        }
2484        if matches!(
2485            candidate.kind(),
2486            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2487        ) {
2488            if let Some(scope) = candidate.child_by_field_name("scope") {
2489                pending.push(scope);
2490            }
2491            if let Some(name) = candidate.child_by_field_name("name") {
2492                pending.push(name);
2493            }
2494        }
2495    }
2496    false
2497}
2498
2499fn call_for_function_node(node: Node<'_>) -> Option<Node<'_>> {
2500    let parent = node.parent()?;
2501    (parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node))
2502        .then_some(parent)
2503}
2504
2505fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2506    ctx.target_group.iter().find(|target| {
2507        same_logical_symbol(target, &ctx.spec.target)
2508            && ctx.visibility.is_physically_visible(ctx.file, target)
2509    })
2510}
2511
2512fn target_guided_missing_direct_temporary_type<'tree>(
2513    function: Node<'tree>,
2514    ctx: &ScanCtx<'_>,
2515) -> Option<Node<'tree>> {
2516    let target = physically_visible_type_target(ctx)?;
2517    let component_nodes = cpp_name_component_nodes(function)?;
2518    let terminal = component_nodes.last().copied()?;
2519    if node_text(terminal, ctx.source) != target.identifier() {
2520        return None;
2521    }
2522    let components = component_nodes
2523        .iter()
2524        .map(|component| node_text(*component, ctx.source).to_string())
2525        .collect::<Vec<_>>();
2526    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
2527    indexed_scope_matches_target_name(
2528        &indexed_scope,
2529        &components,
2530        is_globally_qualified_cpp_name(function),
2531        target,
2532    )
2533    .then_some(terminal)
2534}
2535
2536fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
2537    let Some(function) = call.child_by_field_name("function") else {
2538        return;
2539    };
2540    if !matches!(
2541        function.kind(),
2542        "identifier"
2543            | "type_identifier"
2544            | "template_function"
2545            | "template_type"
2546            | "qualified_identifier"
2547            | "scoped_identifier"
2548            | "scoped_type_identifier"
2549    ) {
2550        return;
2551    }
2552    if !type_reference_components_may_name_target(function, ctx) {
2553        return;
2554    }
2555    if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
2556        *ctx.raw_match_count += 1;
2557        for scope in scopes {
2558            push_type_hit(scope, ctx);
2559        }
2560        return;
2561    }
2562    let terminal = function_terminal_node(function);
2563    let name = node_text(terminal, ctx.source);
2564    if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
2565        return;
2566    }
2567    if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
2568        match resolve_declaring_member_owner(
2569            &ctx.analyzer,
2570            ctx.visibility,
2571            ctx.file,
2572            &enclosing_owner,
2573            name,
2574        ) {
2575            EnclosingMemberOwnerResolution::Owner(owner)
2576                if matches!(
2577                    ctx.visibility
2578                        .visible_member_for_owner_name(ctx.file, &owner, name,),
2579                    VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
2580                ) =>
2581            {
2582                return;
2583            }
2584            EnclosingMemberOwnerResolution::Ambiguous => return,
2585            EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
2586        }
2587    }
2588
2589    // A type alias used as a direct temporary (`result_type(value)`) is parsed
2590    // as an ordinary identifier call. Callable lookup can be ambiguous when
2591    // parser recovery flattens a namespace or same-spelled aliases are
2592    // visible from sibling distributions. An exact enclosing class owner
2593    // proves the member alias without treating the call as an arbitrary name;
2594    // retain the shadow and declaration-visibility guards above and below.
2595    if ctx
2596        .analyzer
2597        .type_alias_provider()
2598        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2599        && name == ctx.spec.target.identifier()
2600        && physically_visible_type_target(ctx).is_some()
2601        && !local_type_name_shadows(function, ctx)
2602        && type_alias_owner_matches_structured_reference(function, ctx)
2603        && ctx.visibility.external_type_candidate_visible_in_context(
2604            &ctx.analyzer,
2605            ctx.file,
2606            &ctx.spec.target,
2607            function,
2608        )
2609    {
2610        *ctx.raw_match_count += 1;
2611        push_type_hit(terminal, ctx);
2612        return;
2613    }
2614
2615    let call_resolution = resolve_qualified_call_target(
2616        call,
2617        function,
2618        &ctx.analyzer,
2619        ctx.visibility,
2620        &ctx.ordinary_type_imports,
2621        ctx.file,
2622        ctx.source,
2623    );
2624    match call_resolution {
2625        BareCallTargetResolution::Type(unit) => {
2626            if same_visible_symbol(&unit, &ctx.spec.target) {
2627                *ctx.raw_match_count += 1;
2628                push_type_hit(function, ctx);
2629                return;
2630            }
2631        }
2632        BareCallTargetResolution::FreeFunctions(units)
2633            if units.iter().all(|unit| {
2634                unit.fq_name() == ctx.spec.target.fq_name()
2635                    && ctx
2636                        .visibility
2637                        .callable_is_constructor_declaration(&ctx.analyzer, unit)
2638            }) => {}
2639        BareCallTargetResolution::Ambiguous => {
2640            push_unproven_hit(function, ctx);
2641            return;
2642        }
2643        BareCallTargetResolution::FreeFunctions(_)
2644        | BareCallTargetResolution::UnprovenFreeFunctions(_)
2645        | BareCallTargetResolution::CallableShadow => return,
2646        // Generated `.c` includes can leave ordinary callable resolution with
2647        // no active callable even when target-preserving type resolution can
2648        // prove the constructor's class. Let the structured type fallback
2649        // below make that decision.
2650        BareCallTargetResolution::Missing => {}
2651    }
2652    let target_resolution = resolve_type_node_lexically_for_target(
2653        function,
2654        &ctx.analyzer,
2655        ctx.visibility,
2656        &ctx.ordinary_type_imports,
2657        ctx.file,
2658        ctx.source,
2659        &ctx.spec.target,
2660        Some(&ctx.lexical_scope_cache),
2661        ctx.recovered_sentinel_scope(function).as_deref(),
2662    );
2663    match target_resolution {
2664        LexicalTypeResolution::Resolved {
2665            unit, candidates, ..
2666        } if same_visible_symbol(&unit, &ctx.spec.target)
2667            || candidates
2668                .iter()
2669                .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
2670        {
2671            *ctx.raw_match_count += 1;
2672            push_type_hit(function, ctx);
2673        }
2674        LexicalTypeResolution::Missing => {
2675            if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
2676                .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
2677            {
2678                *ctx.raw_match_count += 1;
2679                push_type_hit(hit, ctx);
2680            }
2681        }
2682        LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
2683    }
2684}
2685
2686pub enum BareCallTargetResolution {
2687    Type(CodeUnit),
2688    FreeFunctions(Vec<CodeUnit>),
2689    UnprovenFreeFunctions(Vec<CodeUnit>),
2690    CallableShadow,
2691    Ambiguous,
2692    Missing,
2693}
2694
2695pub enum BlockUsingCallTargetResolution {
2696    Target(BareCallTargetResolution),
2697    Unindexed(Vec<String>),
2698    Ambiguous,
2699}
2700
2701#[allow(clippy::too_many_arguments)]
2702fn resolve_qualified_call_target(
2703    call: Node<'_>,
2704    function: Node<'_>,
2705    analyzer: &CppGraphSource<'_>,
2706    visibility: &VisibilityIndex,
2707    ordinary_type_imports: &OrdinaryTypeImportCell,
2708    file: &ProjectFile,
2709    source: &str,
2710) -> BareCallTargetResolution {
2711    if matches!(function.kind(), "identifier" | "template_function") {
2712        return resolve_bare_call_target(
2713            call,
2714            function,
2715            analyzer,
2716            visibility,
2717            ordinary_type_imports,
2718            file,
2719            source,
2720        );
2721    }
2722    if !matches!(
2723        function.kind(),
2724        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2725    ) {
2726        return BareCallTargetResolution::Missing;
2727    }
2728    let terminal = function_terminal_node(function);
2729    let name = node_text(terminal, source);
2730    let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
2731        return BareCallTargetResolution::Missing;
2732    };
2733    components.push(name.to_string());
2734    let qualified_name = components.join("::");
2735    let type_resolution = resolve_type_node_lexically(
2736        function,
2737        analyzer,
2738        visibility,
2739        ordinary_type_imports,
2740        file,
2741        source,
2742    );
2743    let same_name_resolves_to_type = matches!(
2744        &type_resolution,
2745        LexicalTypeResolution::Resolved {
2746            unit,
2747            candidates,
2748            ..
2749        } if cpp_name_for(unit) == qualified_name
2750            || candidates
2751                .iter()
2752                .any(|candidate| cpp_name_for(candidate) == qualified_name)
2753    );
2754    let has_explicit_template_arguments =
2755        cpp_template_reference_arguments(function, source).is_some();
2756    let candidates = visibility
2757        .visible_identifier_candidates(file, name)
2758        .filter(|candidate| {
2759            candidate.is_function()
2760                && type_owner_of(analyzer, candidate).is_none()
2761                && !(same_name_resolves_to_type
2762                    && (visibility.callable_is_constructor_declaration(analyzer, candidate)
2763                        || has_explicit_template_arguments
2764                            && visibility
2765                                .callable_is_deduction_guide_declaration(analyzer, candidate)))
2766                && cpp_name_for(candidate) == qualified_name
2767                && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
2768        })
2769        .cloned()
2770        .collect::<Vec<_>>();
2771    if !candidates.is_empty() {
2772        return resolve_callable_candidates(
2773            candidates,
2774            visibility.call_arity_evidence(file, call, source).exact(),
2775            call.start_byte(),
2776            analyzer,
2777            visibility,
2778            file,
2779        );
2780    }
2781    match type_resolution {
2782        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2783        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2784        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2785    }
2786}
2787
2788fn binding_free_function_candidates(
2789    binding: &OrdinaryTypeImport,
2790    active_bindings: &[&OrdinaryTypeImport],
2791    analyzer: &CppGraphSource<'_>,
2792    visibility: &VisibilityIndex<'_>,
2793    file: &ProjectFile,
2794    name: &str,
2795    reference_byte: usize,
2796) -> Vec<CodeUnit> {
2797    let Some(qualified) = binding.resolved_target_components.as_ref() else {
2798        return Vec::new();
2799    };
2800    let mut targets = Vec::new();
2801    match binding.target {
2802        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
2803        EffectiveUsingTarget::Namespace { .. } => {
2804            let mut stack = vec![qualified.clone()];
2805            let mut visited = HashSet::default();
2806            while let Some(namespace) = stack.pop() {
2807                if !visited.insert(namespace.clone()) {
2808                    continue;
2809                }
2810                let mut target = namespace.clone();
2811                target.push(name.to_string());
2812                targets.push(target);
2813                stack.extend(active_bindings.iter().filter_map(|candidate| {
2814                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
2815                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
2816                    .then(|| candidate.resolved_target_components.clone())
2817                    .flatten()
2818                }));
2819            }
2820        }
2821    }
2822    targets
2823        .into_iter()
2824        .flat_map(|target| {
2825            let qualified_name = target.join("::");
2826            visibility
2827                .visible_identifier_candidates(file, name)
2828                .filter(move |candidate| {
2829                    candidate.is_function()
2830                        && type_owner_of(analyzer, candidate).is_none()
2831                        && cpp_name_for(candidate) == qualified_name
2832                        && visibility.declaration_visible_at(
2833                            analyzer,
2834                            file,
2835                            candidate,
2836                            reference_byte,
2837                        )
2838                })
2839                .cloned()
2840        })
2841        .collect()
2842}
2843
2844/// Collapse the candidates unqualified lookup found to one entry per logical
2845/// callable, keeping the first spelling of each.
2846///
2847/// A header declaration and its out-of-line body spell one parameter type
2848/// differently often enough that the persisted signature strings disagree, so
2849/// the string triple alone reports one C++ declaration as an overload set and
2850/// an unproven argument count then turns it into ambiguity.
2851/// `same_logical_callable` answers the string question first and resolves the
2852/// written parameter names only when the strings differ (#2010).
2853fn dedupe_callable_candidates(
2854    candidates: &mut Vec<CodeUnit>,
2855    analyzer: &CppGraphSource<'_>,
2856    visibility: &VisibilityIndex<'_>,
2857) {
2858    let mut deduped = Vec::with_capacity(candidates.len());
2859    for candidate in candidates.drain(..) {
2860        if !deduped
2861            .iter()
2862            .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
2863        {
2864            deduped.push(candidate);
2865        }
2866    }
2867    *candidates = deduped;
2868}
2869
2870fn resolve_callable_candidates(
2871    candidates: Vec<CodeUnit>,
2872    call_arity: Option<usize>,
2873    reference_byte: usize,
2874    analyzer: &CppGraphSource<'_>,
2875    visibility: &VisibilityIndex<'_>,
2876    file: &ProjectFile,
2877) -> BareCallTargetResolution {
2878    let mut candidates = candidates;
2879    dedupe_callable_candidates(&mut candidates, analyzer, visibility);
2880    if candidates.is_empty() {
2881        return BareCallTargetResolution::Missing;
2882    }
2883    let Some(call_arity) = call_arity else {
2884        // An unproven argument count cannot create ambiguity where lookup found
2885        // exactly one name binding: there is nothing to be ambiguous between.
2886        // C has no overloading at all, and a lone C++ candidate is the only
2887        // declaration unqualified lookup reached, so arity cannot pick another
2888        // one (#1811). Keeping it unproven discarded the proven candidate and
2889        // answered `ambiguous` with an empty definition list.
2890        if candidates.len() == 1 {
2891            return BareCallTargetResolution::FreeFunctions(candidates);
2892        }
2893        return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
2894    };
2895    let applicable = candidates
2896        .into_iter()
2897        .filter(|candidate| {
2898            visibility
2899                .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
2900                .is_some_and(|arity| arity.accepts(call_arity))
2901        })
2902        .collect::<Vec<_>>();
2903    if applicable.is_empty() {
2904        BareCallTargetResolution::CallableShadow
2905    } else {
2906        BareCallTargetResolution::FreeFunctions(applicable)
2907    }
2908}
2909
2910fn resolve_direct_type_candidates(
2911    candidates: Vec<(CodeUnit, Vec<String>)>,
2912    analyzer: &CppGraphSource<'_>,
2913    visibility: &VisibilityIndex<'_>,
2914    file: &ProjectFile,
2915) -> BareCallTargetResolution {
2916    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
2917    for candidate in candidates {
2918        if !logical
2919            .iter()
2920            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
2921        {
2922            logical.push(candidate);
2923        }
2924    }
2925    let [(target, components)] = logical.as_slice() else {
2926        return if logical.is_empty() {
2927            BareCallTargetResolution::Missing
2928        } else {
2929            BareCallTargetResolution::Ambiguous
2930        };
2931    };
2932    match visibility
2933        .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
2934    {
2935        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2936        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2937        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2938    }
2939}
2940
2941/// Resolve a direct using-declaration in the nearest concrete block before
2942/// class-member lookup. A block declaration such as `using std::swap;` adds
2943/// that name to the block scope and hides a same-named member. If the imported
2944/// target is not indexed, retain its structured path as boundary evidence.
2945#[allow(clippy::too_many_arguments)]
2946pub fn resolve_block_using_call_target(
2947    call: Node<'_>,
2948    function: Node<'_>,
2949    analyzer: &CppGraphSource<'_>,
2950    visibility: &VisibilityIndex<'_>,
2951    ordinary_type_imports: &OrdinaryTypeImportCell,
2952    file: &ProjectFile,
2953    source: &str,
2954) -> Option<BlockUsingCallTargetResolution> {
2955    if !matches!(function.kind(), "identifier" | "template_function") {
2956        return None;
2957    }
2958    let name = node_text(function_terminal_node(function), source);
2959    if name.is_empty() {
2960        return None;
2961    }
2962    let bindings = effective_using_bindings_for_name(
2963        visibility,
2964        ordinary_type_imports,
2965        file,
2966        function,
2967        source,
2968        name,
2969    );
2970    let block_bindings = bindings
2971        .iter()
2972        .filter(|binding| {
2973            binding.namespace_scope.is_none()
2974                && binding.block_scope
2975                && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
2976        })
2977        .collect::<Vec<_>>();
2978    if block_bindings.is_empty() {
2979        return None;
2980    }
2981    let lexical_scope =
2982        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2983            LexicalScopeResolution::Resolved(scope) => scope,
2984            LexicalScopeResolution::Ambiguous => {
2985                return Some(BlockUsingCallTargetResolution::Ambiguous);
2986            }
2987            LexicalScopeResolution::Missing => return None,
2988        };
2989    let reference_guards = preprocessor_guard_environment(function, source);
2990    let active = block_bindings
2991        .into_iter()
2992        .filter(|binding| {
2993            effective_using_binding_active(
2994                binding,
2995                function,
2996                &lexical_scope,
2997                reference_guards.as_ref(),
2998                visibility,
2999                file,
3000            )
3001        })
3002        .collect::<Vec<_>>();
3003    let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3004    let at_tier = active
3005        .into_iter()
3006        .filter(|binding| binding.scope_depth == depth)
3007        .collect::<Vec<_>>();
3008    let callable_candidates = at_tier
3009        .iter()
3010        .flat_map(|binding| {
3011            binding_free_function_candidates(
3012                binding,
3013                &[],
3014                analyzer,
3015                visibility,
3016                file,
3017                name,
3018                call.start_byte(),
3019            )
3020        })
3021        .collect::<Vec<_>>();
3022    if !callable_candidates.is_empty() {
3023        return Some(BlockUsingCallTargetResolution::Target(
3024            resolve_callable_candidates(
3025                callable_candidates,
3026                visibility.call_arity_evidence(file, call, source).exact(),
3027                call.start_byte(),
3028                analyzer,
3029                visibility,
3030                file,
3031            ),
3032        ));
3033    }
3034    let type_candidates = at_tier
3035        .iter()
3036        .flat_map(|binding| {
3037            binding_type_candidates(
3038                binding,
3039                &[],
3040                analyzer,
3041                visibility,
3042                file,
3043                name,
3044                None,
3045                call.start_byte(),
3046            )
3047        })
3048        .collect::<Vec<_>>();
3049    if !type_candidates.is_empty() {
3050        return Some(BlockUsingCallTargetResolution::Target(
3051            resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3052        ));
3053    }
3054
3055    let mut unindexed = Vec::new();
3056    for binding in at_tier {
3057        let Some(components) = binding.resolved_target_components.as_ref() else {
3058            continue;
3059        };
3060        if !unindexed.contains(components) {
3061            unindexed.push(components.clone());
3062        }
3063    }
3064    match unindexed.as_slice() {
3065        [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3066        [] => None,
3067        _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3068    }
3069}
3070
3071#[allow(clippy::too_many_arguments)]
3072pub fn resolve_bare_call_target(
3073    call: Node<'_>,
3074    function: Node<'_>,
3075    analyzer: &CppGraphSource<'_>,
3076    visibility: &VisibilityIndex<'_>,
3077    ordinary_type_imports: &OrdinaryTypeImportCell,
3078    file: &ProjectFile,
3079    source: &str,
3080) -> BareCallTargetResolution {
3081    if !matches!(function.kind(), "identifier" | "template_function") {
3082        return BareCallTargetResolution::Missing;
3083    }
3084    let terminal = function_terminal_node(function);
3085    let name = node_text(terminal, source);
3086    if name.is_empty() {
3087        return BareCallTargetResolution::Missing;
3088    }
3089    let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3090    let lexical_scope =
3091        match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3092            LexicalScopeResolution::Resolved(scope) => scope,
3093            LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3094            LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3095        };
3096    let type_resolution = resolve_type_node_lexically(
3097        function,
3098        analyzer,
3099        visibility,
3100        ordinary_type_imports,
3101        file,
3102        source,
3103    );
3104    let type_components = match &type_resolution {
3105        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3106        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3107    };
3108    let direct_type_resolution = visibility.resolve_type_components_lexically(
3109        analyzer,
3110        file,
3111        &[name.to_string()],
3112        false,
3113        &lexical_scope,
3114    );
3115    let direct_type_components = match &direct_type_resolution {
3116        LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3117        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3118    };
3119    let has_explicit_template_arguments =
3120        cpp_template_reference_arguments(function, source).is_some();
3121    let bindings = effective_using_bindings_for_name(
3122        visibility,
3123        ordinary_type_imports,
3124        file,
3125        function,
3126        source,
3127        name,
3128    );
3129    // Guard ancestry climbs the whole ancestor chain and each `Node::parent`
3130    // step re-descends from the root (#1927): with no bindings both filters
3131    // below select nothing, so the environment is never consulted.
3132    let function_guards = if bindings.is_empty() {
3133        None
3134    } else {
3135        preprocessor_guard_environment(function, source)
3136    };
3137    let active_bindings = bindings
3138        .iter()
3139        .filter(|binding| {
3140            effective_using_binding_active(
3141                binding,
3142                function,
3143                &lexical_scope,
3144                function_guards.as_ref(),
3145                visibility,
3146                file,
3147            )
3148        })
3149        .collect::<Vec<_>>();
3150    let transitive_bindings = bindings
3151        .iter()
3152        .filter(|binding| {
3153            effective_using_binding_guards_active(
3154                binding,
3155                function.start_byte(),
3156                function_guards.as_ref(),
3157                visibility,
3158                file,
3159            ) && (binding.namespace_scope.is_some()
3160                || (binding.scope_start <= function.start_byte()
3161                    && function.end_byte() <= binding.scope_end))
3162        })
3163        .collect::<Vec<_>>();
3164    let mut concrete_depths = active_bindings
3165        .iter()
3166        .filter(|binding| binding.namespace_scope.is_none())
3167        .map(|binding| binding.scope_depth)
3168        .collect::<Vec<_>>();
3169    concrete_depths.sort_unstable();
3170    concrete_depths.dedup();
3171    for depth in concrete_depths.into_iter().rev() {
3172        let at_tier = active_bindings
3173            .iter()
3174            .copied()
3175            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3176        let direct = at_tier
3177            .clone()
3178            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3179            .flat_map(|binding| {
3180                binding_free_function_candidates(
3181                    binding,
3182                    &transitive_bindings,
3183                    analyzer,
3184                    visibility,
3185                    file,
3186                    name,
3187                    call.start_byte(),
3188                )
3189            })
3190            .collect::<Vec<_>>();
3191        if !direct.is_empty() {
3192            return resolve_callable_candidates(
3193                direct,
3194                call_arity,
3195                call.start_byte(),
3196                analyzer,
3197                visibility,
3198                file,
3199            );
3200        }
3201        let direct_types = at_tier
3202            .clone()
3203            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3204            .flat_map(|binding| {
3205                binding_type_candidates(
3206                    binding,
3207                    &transitive_bindings,
3208                    analyzer,
3209                    visibility,
3210                    file,
3211                    name,
3212                    None,
3213                    call.start_byte(),
3214                )
3215            })
3216            .collect::<Vec<_>>();
3217        if !direct_types.is_empty() {
3218            // `resolve_direct_type_candidates` never consults the argument
3219            // count: it answers the one type the name binds to, or reports the
3220            // competing types. An unknown count therefore cannot make this
3221            // ambiguous (#1812).
3222            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3223        }
3224        let directives = at_tier
3225            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3226            .flat_map(|binding| {
3227                binding_free_function_candidates(
3228                    binding,
3229                    &transitive_bindings,
3230                    analyzer,
3231                    visibility,
3232                    file,
3233                    name,
3234                    call.start_byte(),
3235                )
3236            })
3237            .collect::<Vec<_>>();
3238        if !directives.is_empty() {
3239            return resolve_callable_candidates(
3240                directives,
3241                call_arity,
3242                call.start_byte(),
3243                analyzer,
3244                visibility,
3245                file,
3246            );
3247        }
3248    }
3249    for prefix_len in (0..=lexical_scope.len()).rev() {
3250        let mut qualified = lexical_scope[..prefix_len].to_vec();
3251        qualified.push(name.to_string());
3252        let same_name_resolves_to_type = direct_type_components
3253            .is_some_and(|components| components == qualified.as_slice())
3254            || type_components.is_some_and(|components| components == qualified.as_slice());
3255        let mut direct = visibility
3256            .visible_identifier_candidates(file, name)
3257            .filter(|candidate| {
3258                candidate.is_function()
3259                    && type_owner_of(analyzer, candidate).is_none()
3260                    && !(same_name_resolves_to_type
3261                        && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3262                            || has_explicit_template_arguments
3263                                && visibility
3264                                    .callable_is_deduction_guide_declaration(analyzer, candidate)))
3265                    && cpp_name_for(candidate) == qualified.join("::")
3266                    && if analyzer.reference_uses_c_semantics(file) {
3267                        visibility.declaration_visible_for_c_forward_call(
3268                            analyzer,
3269                            file,
3270                            candidate,
3271                            call.start_byte(),
3272                        )
3273                    } else {
3274                        visibility.declaration_visible_at(
3275                            analyzer,
3276                            file,
3277                            candidate,
3278                            call.start_byte(),
3279                        )
3280                    }
3281            })
3282            .cloned()
3283            .collect::<Vec<_>>();
3284        let at_tier = active_bindings.iter().copied().filter(|binding| {
3285            binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3286        });
3287        direct.extend(
3288            at_tier
3289                .clone()
3290                .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3291                .flat_map(|binding| {
3292                    binding_free_function_candidates(
3293                        binding,
3294                        &transitive_bindings,
3295                        analyzer,
3296                        visibility,
3297                        file,
3298                        name,
3299                        call.start_byte(),
3300                    )
3301                }),
3302        );
3303        if !direct.is_empty() {
3304            return resolve_callable_candidates(
3305                direct,
3306                call_arity,
3307                call.start_byte(),
3308                analyzer,
3309                visibility,
3310                file,
3311            );
3312        }
3313        let mut direct_types = at_tier
3314            .clone()
3315            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3316            .flat_map(|binding| {
3317                binding_type_candidates(
3318                    binding,
3319                    &transitive_bindings,
3320                    analyzer,
3321                    visibility,
3322                    file,
3323                    name,
3324                    None,
3325                    call.start_byte(),
3326                )
3327            })
3328            .collect::<Vec<_>>();
3329        if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3330            && let LexicalTypeResolution::Resolved {
3331                unit, components, ..
3332            } = &direct_type_resolution
3333        {
3334            direct_types.push((unit.clone(), components.clone()));
3335        }
3336        if !direct_types.is_empty() {
3337            // `resolve_direct_type_candidates` never consults the argument
3338            // count: it answers the one type the name binds to, or reports the
3339            // competing types. An unknown count therefore cannot make this
3340            // ambiguous (#1812).
3341            return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3342        }
3343        let directives = at_tier
3344            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3345            .flat_map(|binding| {
3346                binding_free_function_candidates(
3347                    binding,
3348                    &transitive_bindings,
3349                    analyzer,
3350                    visibility,
3351                    file,
3352                    name,
3353                    call.start_byte(),
3354                )
3355            })
3356            .collect::<Vec<_>>();
3357        if !directives.is_empty() {
3358            return resolve_callable_candidates(
3359                directives,
3360                call_arity,
3361                call.start_byte(),
3362                analyzer,
3363                visibility,
3364                file,
3365            );
3366        }
3367        if type_components.is_some_and(|components| components == qualified.as_slice()) {
3368            // The lexical type resolution below already answers with the single
3369            // type, or with its own ambiguity verdict; the argument count adds
3370            // nothing to that decision (#1812).
3371            return match type_resolution {
3372                LexicalTypeResolution::Resolved { unit, .. } => {
3373                    BareCallTargetResolution::Type(unit)
3374                }
3375                LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3376                LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3377            };
3378        }
3379    }
3380    // Every lookup tier is exhausted: no callable and no type candidate was
3381    // found. Reporting that as `Ambiguous` claimed an ambiguity between nothing
3382    // at all, and its early return in get_definition preempted the same-file
3383    // macro fallback - so a call to a macro defined in the referencing file
3384    // (libyang's `RBN_RIGHT`, glpk's `#define error dmx_error`) could never
3385    // resolve once an unresolvable include made the argument count unknown.
3386    // A no-candidate outcome is Missing, which is what makes the fallback
3387    // reachable (#1812).
3388    match type_resolution {
3389        LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3390        LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3391        LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3392    }
3393}
3394
3395fn static_qualifier_type_scopes<'tree>(
3396    node: Node<'tree>,
3397    ctx: &ScanCtx<'_>,
3398) -> Option<Vec<Node<'tree>>> {
3399    if !matches!(
3400        node.kind(),
3401        "qualified_identifier" | "scoped_type_identifier"
3402    ) {
3403        return None;
3404    }
3405    // `maybe_record_type_hit` rejects nested type nodes before this helper, so
3406    // this root contains every structured component needed for prefix lookup.
3407    debug_assert!(!is_nested_type_node(node));
3408    let qualified = qualified_owner_components(node, ctx.source)?;
3409    static_qualifier_type_scopes_for_components(node, qualified, ctx)
3410}
3411
3412fn static_qualifier_type_scopes_for_components<'tree>(
3413    node: Node<'tree>,
3414    qualified: QualifiedOwnerComponents<'tree>,
3415    ctx: &ScanCtx<'_>,
3416) -> Option<Vec<Node<'tree>>> {
3417    if !qualified.global
3418        && qualified.names.first().is_some_and(|name| {
3419            name == ctx.spec.target.identifier()
3420                && qualified
3421                    .nodes
3422                    .first()
3423                    .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
3424        })
3425    {
3426        return None;
3427    }
3428    let mut matches = Vec::new();
3429    let mut inherited_injected_name_is_shadowed = false;
3430    for component_count in 1..=qualified.names.len() {
3431        let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3432            node,
3433            &qualified.names[..component_count],
3434            qualified.global,
3435            &ctx.analyzer,
3436            ctx.visibility,
3437            &ctx.ordinary_type_imports,
3438            ctx.file,
3439            ctx.source,
3440            &ctx.spec.target,
3441            false,
3442            Some(&ctx.lexical_scope_cache),
3443        );
3444        match resolution {
3445            LexicalTypeResolution::Resolved {
3446                unit, candidates, ..
3447            } if (!ctx
3448                .analyzer
3449                .type_alias_provider()
3450                .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3451                || ctx.visibility.external_type_candidate_visible_in_context(
3452                    &ctx.analyzer,
3453                    ctx.file,
3454                    &ctx.spec.target,
3455                    node,
3456                ))
3457                && (same_visible_symbol(&unit, &ctx.spec.target)
3458                    || candidates
3459                        .iter()
3460                        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3461                && target_alias_candidates_visible(&candidates, node, ctx) =>
3462            {
3463                let matched =
3464                    qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3465                if !template_type_component_preserves_target(matched, &candidates, ctx) {
3466                    continue;
3467                }
3468                if !matches.iter().any(|existing: &Node<'_>| {
3469                    existing.start_byte() == matched.start_byte()
3470                        && existing.end_byte() == matched.end_byte()
3471                }) {
3472                    matches.push(matched);
3473                }
3474            }
3475            // The ordinary lexical resolver can remain ambiguous when the
3476            // qualified terminal is an alias whose canonical target is not
3477            // indexed (for example, `Hash::Digest` aliases an external
3478            // `std::array`). The target-guided path below still requires one
3479            // physically visible logical class for every emitted prefix.
3480            LexicalTypeResolution::Ambiguous => {
3481                return (!inherited_injected_name_is_shadowed)
3482                    .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3483                    .flatten()
3484                    .map(|scope| vec![scope])
3485                    .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3486            }
3487            LexicalTypeResolution::Resolved { .. } => {
3488                if let Some(matched) =
3489                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3490                {
3491                    matches.push(matched);
3492                }
3493                inherited_injected_name_is_shadowed |= component_count == 1;
3494            }
3495            LexicalTypeResolution::Missing => {
3496                if let Some(matched) =
3497                    target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3498                {
3499                    matches.push(matched);
3500                }
3501            }
3502        }
3503    }
3504    if matches.is_empty() {
3505        (!inherited_injected_name_is_shadowed)
3506            .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3507            .flatten()
3508            .map(|scope| vec![scope])
3509            .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3510    } else {
3511        Some(matches)
3512    }
3513}
3514
3515/// Recover a class owner in a qualified expression when guard-aware lookup
3516/// cannot prove the owner. Keep the hit on the owner component, not the member.
3517fn target_guided_unproven_qualified_value_owner_scope<'tree>(
3518    node: Node<'tree>,
3519    ctx: &ScanCtx<'_>,
3520) -> Option<Node<'tree>> {
3521    let target = physically_visible_type_target(ctx)?;
3522    if !target.is_class() {
3523        return None;
3524    }
3525    let qualified = qualified_owner_components(node, ctx.source)?;
3526    let lexical_scope = match enclosing_lexical_scope_components(
3527        node,
3528        &ctx.analyzer,
3529        ctx.visibility,
3530        ctx.file,
3531        ctx.source,
3532    ) {
3533        LexicalScopeResolution::Resolved(scope) => scope,
3534        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3535            enclosing_namespace_components(node, ctx.source)
3536        }
3537    };
3538    let LexicalTypeResolution::Resolved {
3539        unit, candidates, ..
3540    } = ctx.visibility.resolve_type_components_lexically_for_target(
3541        &ctx.analyzer,
3542        ctx.file,
3543        &qualified.names,
3544        qualified.global,
3545        &lexical_scope,
3546        target,
3547    )
3548    else {
3549        return None;
3550    };
3551    (same_visible_symbol(&unit, target)
3552        || candidates
3553            .iter()
3554            .any(|candidate| same_visible_symbol(candidate, target)))
3555    .then(|| qualified.nodes.last().copied())
3556    .flatten()
3557}
3558
3559/// Resolve a nested class-owned alias when the indexed alias path is not a
3560/// standalone type candidate. The C++ index stores `basic_json::type_error`
3561/// as a synthetic child of `basic_json`, while source can qualify it through
3562/// a class alias such as `json::type_error`. Resolve the owner prefix first,
3563/// then canonicalize the structured member alias against the requested type.
3564fn target_guided_nested_alias_type_scope<'tree>(
3565    node: Node<'tree>,
3566    qualified: &QualifiedOwnerComponents<'tree>,
3567    component_count: usize,
3568    ctx: &ScanCtx<'_>,
3569) -> Option<Node<'tree>> {
3570    if component_count < 2 {
3571        return None;
3572    }
3573    let (owner_components, member_name) =
3574        qualified.names[..component_count].split_at(component_count - 1);
3575    let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
3576        node,
3577        owner_components,
3578        qualified.global,
3579        &ctx.analyzer,
3580        ctx.visibility,
3581        &ctx.ordinary_type_imports,
3582        ctx.file,
3583        ctx.source,
3584    ) else {
3585        return None;
3586    };
3587    let member_name = member_name.first()?;
3588    let alias_provider = ctx.analyzer.type_alias_provider()?;
3589    ctx.visibility
3590        .visible_members_for_owner_name(ctx.file, &owner, member_name)
3591        .into_iter()
3592        .filter(|member| alias_provider.is_type_alias(member))
3593        .find(|member| {
3594            let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
3595                &ctx.analyzer,
3596                ctx.file,
3597                member,
3598                node,
3599            ) || ctx
3600                .visibility
3601                .external_type_candidate_guard_compatible_in_context(
3602                    &ctx.analyzer,
3603                    ctx.file,
3604                    member,
3605                    node,
3606                );
3607            if !member_visible {
3608                return false;
3609            }
3610            same_visible_symbol(member, &ctx.spec.target)
3611                || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
3612        })
3613        .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
3614}
3615
3616fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
3617    if ctx.visibility.structured_class_alias_resolves_to_target(
3618        &ctx.analyzer,
3619        ctx.file,
3620        candidate,
3621        &ctx.spec.target,
3622    ) {
3623        return ctx.spec.target.clone();
3624    }
3625    let structured = ctx
3626        .visibility
3627        .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
3628    if let Some(canonical) = structured
3629        .as_ref()
3630        .filter(|canonical| !same_visible_symbol(canonical, candidate))
3631    {
3632        return canonical.clone();
3633    }
3634    structured.unwrap_or_else(|| candidate.clone())
3635}
3636
3637/// Preserve the alias component of a qualified reference when the alias target
3638/// is a dependent nested type and forward lookup retains its primary template
3639/// as the bounded identity.
3640fn target_guided_dependent_alias_qualifier_scope<'tree>(
3641    node: Node<'tree>,
3642    ctx: &ScanCtx<'_>,
3643) -> Option<Node<'tree>> {
3644    if !matches!(
3645        node.kind(),
3646        "qualified_identifier" | "scoped_type_identifier"
3647    ) {
3648        return None;
3649    }
3650    let target = physically_visible_type_target(ctx)?;
3651    let alias_provider = ctx.analyzer.type_alias_provider()?;
3652    if !target.is_class() || alias_provider.is_type_alias(target) {
3653        return None;
3654    }
3655    let nodes = cpp_name_component_nodes(node)?;
3656    let names = nodes
3657        .iter()
3658        .map(|component| node_text(*component, ctx.source).to_string())
3659        .collect::<Vec<_>>();
3660    let global = is_globally_qualified_cpp_name(node);
3661    let lexical_scope = match enclosing_lexical_scope_components(
3662        node,
3663        &ctx.analyzer,
3664        ctx.visibility,
3665        ctx.file,
3666        ctx.source,
3667    ) {
3668        LexicalScopeResolution::Resolved(scope) => scope,
3669        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3670            enclosing_namespace_components(node, ctx.source)
3671        }
3672    };
3673    for component_count in 1..=names.len() {
3674        let components = &names[..component_count];
3675        let name = components.last()?;
3676        let candidates = ctx
3677            .visibility
3678            .visible_identifier_candidates(ctx.file, name)
3679            .filter(|candidate| alias_provider.is_type_alias(candidate))
3680            .filter(|candidate| {
3681                ctx.visibility.is_physically_visible(ctx.file, candidate)
3682                    && ctx
3683                        .visibility
3684                        .external_type_candidate_guard_compatible_in_context(
3685                            &ctx.analyzer,
3686                            ctx.file,
3687                            candidate,
3688                            node,
3689                        )
3690            })
3691            .filter(|candidate| {
3692                let candidate_components = canonical_cpp_scope_components(candidate);
3693                lexical_component_tiers(components, global, &lexical_scope)
3694                    .any(|tier| tier == candidate_components)
3695                    || (component_count == 1
3696                        && member_alias_owner_matches_reference_for(
3697                            candidate,
3698                            nodes[component_count - 1],
3699                            ctx,
3700                        ))
3701            })
3702            .filter(|candidate| {
3703                ctx.visibility.structured_class_alias_path_preserves_target(
3704                    &ctx.analyzer,
3705                    ctx.file,
3706                    candidate,
3707                    target,
3708                )
3709            });
3710        let mut aliases: Vec<&CodeUnit> = Vec::new();
3711        for candidate in candidates {
3712            if !aliases
3713                .iter()
3714                .any(|existing| same_logical_symbol(existing, candidate))
3715            {
3716                aliases.push(candidate);
3717            }
3718        }
3719        if aliases.len() == 1 {
3720            return nodes.get(component_count - 1).copied();
3721        }
3722        if aliases.len() > 1 {
3723            return None;
3724        }
3725    }
3726    None
3727}
3728
3729/// Preserve an unqualified class-owned alias when its dependent target path
3730/// retains the requested primary template as the bounded forward identity.
3731fn target_guided_dependent_class_alias_leaf<'tree>(
3732    node: Node<'tree>,
3733    ctx: &ScanCtx<'_>,
3734) -> Option<Node<'tree>> {
3735    if node.kind() != "type_identifier"
3736        || is_declaration_name(node)
3737        || local_type_name_shadows(node, ctx)
3738    {
3739        return None;
3740    }
3741    let target = physically_visible_type_target(ctx)?;
3742    let alias_provider = ctx.analyzer.type_alias_provider()?;
3743    if !target.is_class() || alias_provider.is_type_alias(target) {
3744        return None;
3745    }
3746    let name = node_text(node, ctx.source);
3747    let aliases = ctx
3748        .visibility
3749        .visible_identifier_candidates(ctx.file, name)
3750        .filter(|candidate| alias_provider.is_type_alias(candidate))
3751        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3752        .filter(|candidate| {
3753            ctx.visibility.is_physically_visible(ctx.file, candidate)
3754                && ctx
3755                    .visibility
3756                    .external_type_candidate_guard_compatible_in_context(
3757                        &ctx.analyzer,
3758                        ctx.file,
3759                        candidate,
3760                        node,
3761                    )
3762        })
3763        .filter(|candidate| {
3764            ctx.visibility.structured_class_alias_path_preserves_target(
3765                &ctx.analyzer,
3766                ctx.file,
3767                candidate,
3768                target,
3769            )
3770        })
3771        .collect::<Vec<_>>();
3772    matches!(aliases.as_slice(), [_]).then_some(node)
3773}
3774
3775/// Recover a namespace alias whose guard state blocks ordinary visibility.
3776/// Require one visible canonical target and an exact structured alias path.
3777fn target_guided_unproven_alias_type_reference<'tree>(
3778    node: Node<'tree>,
3779    candidates: &[CodeUnit],
3780    ctx: &ScanCtx<'_>,
3781) -> Option<Node<'tree>> {
3782    let template_arguments = cpp_template_reference_arguments(node, ctx.source);
3783    let target = physically_visible_type_target(ctx)?;
3784    if !target.is_class() {
3785        return None;
3786    }
3787    let alias_provider = ctx.analyzer.type_alias_provider()?;
3788    let (components, _) = type_reference_components(node, ctx.source)?;
3789    let hit = template_arguments
3790        .as_ref()
3791        .and_then(|_| template_reference_name_node(node))
3792        .map(function_terminal_node)
3793        .unwrap_or_else(|| function_terminal_node(node));
3794    candidates
3795        .iter()
3796        .filter(|candidate| {
3797            alias_provider.is_type_alias(candidate)
3798                && ctx.visibility.is_physically_visible(ctx.file, candidate)
3799                && canonical_cpp_scope_components(candidate) == components
3800        })
3801        .find(|candidate| {
3802            template_arguments.as_ref().map_or_else(
3803                || {
3804                    same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3805                        || ctx.visibility.structured_alias_primary_preserves_target(
3806                            &ctx.analyzer,
3807                            ctx.file,
3808                            candidate,
3809                            target,
3810                        )
3811                },
3812                |arguments| {
3813                    ctx.visibility.template_alias_arguments_preserve_target(
3814                        &ctx.analyzer,
3815                        ctx.file,
3816                        candidate,
3817                        arguments,
3818                        target,
3819                    )
3820                },
3821            )
3822        })
3823        .map(|_| hit)
3824}
3825
3826fn target_alias_candidates_visible(
3827    candidates: &[CodeUnit],
3828    reference: Node<'_>,
3829    ctx: &ScanCtx<'_>,
3830) -> bool {
3831    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
3832        return true;
3833    };
3834    if candidates.iter().any(|candidate| {
3835        !alias_provider.is_type_alias(candidate)
3836            && ctx.visibility.same_template_member_identity(
3837                &ctx.analyzer,
3838                candidate,
3839                &ctx.spec.target,
3840            )
3841    }) {
3842        return true;
3843    }
3844    let target_aliases = candidates
3845        .iter()
3846        .filter(|candidate| {
3847            alias_provider.is_type_alias(candidate)
3848                && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3849        })
3850        .collect::<Vec<_>>();
3851    target_aliases.is_empty()
3852        || target_aliases
3853            .iter()
3854            .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
3855}
3856
3857fn type_candidate_visible_at_reference(
3858    candidate: &CodeUnit,
3859    reference: Node<'_>,
3860    ctx: &ScanCtx<'_>,
3861) -> bool {
3862    let class_owned_alias = ctx
3863        .analyzer
3864        .type_alias_provider()
3865        .is_some_and(|provider| provider.is_type_alias(candidate))
3866        && ctx
3867            .analyzer
3868            .parent_of(candidate)
3869            .is_some_and(|owner| owner.is_class());
3870    if class_owned_alias {
3871        let conditional_family = ctx
3872            .visibility
3873            .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
3874        let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
3875            || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
3876            || member_alias_owner_matches_reference_for(candidate, reference, ctx);
3877        let guard_match = ctx
3878            .visibility
3879            .external_type_candidate_guard_compatible_in_context(
3880                &ctx.analyzer,
3881                ctx.file,
3882                candidate,
3883                reference,
3884            );
3885        let general_match = conditional_family
3886            && ctx.visibility.external_type_candidate_visible_in_context(
3887                &ctx.analyzer,
3888                ctx.file,
3889                candidate,
3890                reference,
3891            );
3892        return owner_match && (guard_match || general_match);
3893    }
3894    ctx.visibility.external_type_candidate_visible_in_context(
3895        &ctx.analyzer,
3896        ctx.file,
3897        candidate,
3898        reference,
3899    )
3900}
3901
3902fn unqualified_reference_selects_inherited_alias(
3903    candidate: &CodeUnit,
3904    reference: Node<'_>,
3905    ctx: &ScanCtx<'_>,
3906) -> bool {
3907    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3908        return false;
3909    };
3910    if global || components.len() != 1 {
3911        return false;
3912    }
3913    matches!(
3914        resolve_type_node_lexically_for_target(
3915            reference,
3916            &ctx.analyzer,
3917            ctx.visibility,
3918            &ctx.ordinary_type_imports,
3919            ctx.file,
3920            ctx.source,
3921            candidate,
3922            Some(&ctx.lexical_scope_cache),
3923            ctx.recovered_sentinel_scope(reference).as_deref(),
3924        ),
3925        LexicalTypeResolution::Resolved {
3926            ref unit,
3927            ref candidates,
3928            ..
3929        } if ctx
3930            .visibility
3931            .same_template_member_identity(&ctx.analyzer, unit, candidate)
3932            || candidates.iter().any(|resolved| {
3933                ctx.visibility.same_template_member_identity(
3934                    &ctx.analyzer,
3935                    resolved,
3936                    candidate,
3937                )
3938            })
3939    )
3940}
3941
3942fn qualified_reference_selects_type_candidate(
3943    candidate: &CodeUnit,
3944    reference: Node<'_>,
3945    ctx: &ScanCtx<'_>,
3946) -> bool {
3947    let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3948        return false;
3949    };
3950    if components.len() < 2 {
3951        return false;
3952    }
3953    let candidate_components = canonical_cpp_scope_components(candidate);
3954    let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3955        match enclosing_lexical_scope_components(
3956            reference,
3957            &ctx.analyzer,
3958            ctx.visibility,
3959            ctx.file,
3960            ctx.source,
3961        ) {
3962            LexicalScopeResolution::Resolved(scope) => scope,
3963            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3964                enclosing_namespace_components(reference, ctx.source)
3965            }
3966        }
3967    });
3968    lexical_component_tiers(&components, global, &lexical_scope)
3969        .any(|qualified| qualified == candidate_components)
3970}
3971
3972fn qualified_type_component_hit_node<'tree>(
3973    component: Node<'tree>,
3974    qualified: Node<'tree>,
3975) -> Node<'tree> {
3976    let mut current = component;
3977    while let Some(parent) = current.parent() {
3978        let is_type_name = matches!(
3979            parent.kind(),
3980            "template_type"
3981                | "qualified_identifier"
3982                | "scoped_identifier"
3983                | "scoped_type_identifier"
3984        ) && parent
3985            .child_by_field_name("name")
3986            .is_some_and(|name| same_node(name, current));
3987        if !is_type_name {
3988            break;
3989        }
3990        current = parent;
3991        if same_node(parent, qualified) {
3992            break;
3993        }
3994    }
3995    current
3996}
3997
3998fn template_type_component_preserves_target(
3999    node: Node<'_>,
4000    candidates: &[CodeUnit],
4001    ctx: &ScanCtx<'_>,
4002) -> bool {
4003    template_reference_candidates_select_target(
4004        node,
4005        candidates,
4006        &ctx.analyzer,
4007        ctx.visibility,
4008        ctx.file,
4009        ctx.source,
4010        &ctx.spec.target,
4011    )
4012}
4013
4014fn template_reference_candidates_select_target(
4015    node: Node<'_>,
4016    candidates: &[CodeUnit],
4017    analyzer: &CppGraphSource<'_>,
4018    visibility: &VisibilityIndex<'_>,
4019    file: &ProjectFile,
4020    source: &str,
4021    target: &CodeUnit,
4022) -> bool {
4023    let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4024        return !visibility.is_template_specialization(target);
4025    };
4026    let direct_template_name =
4027        template_reference_name_node(node).map(|name| node_text(name, source));
4028    let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4029        analyzer.type_alias_provider().is_some_and(|provider| {
4030            visibility
4031                .visible_identifier_candidates(file, name)
4032                .filter(|candidate| provider.is_type_alias(candidate))
4033                .any(|candidate| {
4034                    visibility.template_alias_arguments_preserve_target(
4035                        analyzer, file, candidate, &arguments, target,
4036                    )
4037                })
4038        })
4039    });
4040    named_alias_selects_target
4041        || candidates.iter().any(|candidate| {
4042            (same_visible_symbol(candidate, target)
4043                && visibility.is_primary_template(target)
4044                && direct_template_name == Some(candidate.identifier()))
4045                || visibility.template_alias_arguments_preserve_target(
4046                    analyzer, file, candidate, &arguments, target,
4047                )
4048                || visibility
4049                    .resolve_template_arguments(file, candidate.clone(), &arguments)
4050                    .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4051        })
4052}
4053
4054fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4055    let template = if node.kind() == "template_type" {
4056        node
4057    } else {
4058        node.child_by_field_name("name")
4059            .filter(|name| name.kind() == "template_type")?
4060    };
4061    template.child_by_field_name("name")
4062}
4063
4064fn type_resolution_matches_target(
4065    node: Node<'_>,
4066    unit: &CodeUnit,
4067    candidates: &[CodeUnit],
4068    ctx: &ScanCtx<'_>,
4069) -> bool {
4070    type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4071}
4072
4073fn type_resolution_matches_unit_target(
4074    node: Node<'_>,
4075    unit: &CodeUnit,
4076    candidates: &[CodeUnit],
4077    target: &CodeUnit,
4078    ctx: &ScanCtx<'_>,
4079) -> bool {
4080    target_alias_candidates_visible(candidates, node, ctx)
4081        && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4082}
4083
4084/// The identity half of the type-resolution match, without the alias
4085/// visibility gate.
4086///
4087/// Use it only on the without-visibility fallback path, which reports an
4088/// unproven hit. An alias spelling does not contain the target identifier, so
4089/// the name-mention fallback can never recover a rejected alias reference: the
4090/// site would disappear instead of degrading to a reviewable hit.
4091fn type_resolution_identifies_unit_target(
4092    node: Node<'_>,
4093    unit: &CodeUnit,
4094    candidates: &[CodeUnit],
4095    target: &CodeUnit,
4096    ctx: &ScanCtx<'_>,
4097) -> bool {
4098    if !template_alias_owner_matches_reference(node, target, ctx) {
4099        return false;
4100    }
4101    if ctx.visibility.is_template_specialization(target)
4102        && cpp_template_reference_arguments(node, ctx.source).is_some()
4103    {
4104        let selected_unit =
4105            cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4106                ctx.visibility
4107                    .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4108                    .ok()
4109            });
4110        return selected_unit
4111            .as_ref()
4112            .is_some_and(|selected| same_visible_symbol(selected, target))
4113            || template_reference_candidates_select_target(
4114                node,
4115                candidates,
4116                &ctx.analyzer,
4117                ctx.visibility,
4118                ctx.file,
4119                ctx.source,
4120                target,
4121            );
4122    }
4123    ctx.visibility
4124        .same_template_member_identity(&ctx.analyzer, unit, target)
4125        || ctx.visibility.structured_class_alias_resolves_to_target(
4126            &ctx.analyzer,
4127            ctx.file,
4128            unit,
4129            target,
4130        )
4131        || candidates.iter().any(|candidate| {
4132            ctx.visibility
4133                .same_template_member_identity(&ctx.analyzer, candidate, target)
4134                || ctx.visibility.structured_class_alias_resolves_to_target(
4135                    &ctx.analyzer,
4136                    ctx.file,
4137                    candidate,
4138                    target,
4139                )
4140        })
4141}
4142
4143/// Keep a member alias attached to the class specialization that declares it.
4144/// A target-guided lexical lookup can otherwise retain the primary alias when
4145/// the source reference is inside a partial specialization with the same
4146/// unqualified alias name. Compare the indexed template identities instead of
4147/// rendered text or suffixes.
4148fn template_alias_owner_matches_reference(
4149    node: Node<'_>,
4150    target: &CodeUnit,
4151    ctx: &ScanCtx<'_>,
4152) -> bool {
4153    if !ctx
4154        .analyzer
4155        .type_alias_provider()
4156        .is_some_and(|provider| provider.is_type_alias(target))
4157    {
4158        return true;
4159    }
4160    let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4161        return true;
4162    };
4163    if !target_owner.is_class() {
4164        return true;
4165    }
4166    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4167        return true;
4168    };
4169    if !ctx.visibility.is_template_specialization(&target_owner)
4170        && !ctx.visibility.is_template_specialization(&reference_owner)
4171    {
4172        return true;
4173    }
4174    same_visible_symbol(&target_owner, &reference_owner)
4175}
4176
4177fn inherited_injected_class_qualifier_scope<'tree>(
4178    node: Node<'tree>,
4179    ctx: &ScanCtx<'_>,
4180) -> Option<Node<'tree>> {
4181    let qualified = qualified_owner_components(node, ctx.source)?;
4182    if qualified.global || qualified.names.is_empty() {
4183        return None;
4184    }
4185    let injected_name = &qualified.names[0];
4186    if !ctx.spec.target.is_class()
4187        || ctx.spec.target.identifier() != injected_name
4188        || physically_visible_type_target(ctx).is_none()
4189    {
4190        return None;
4191    }
4192    let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4193    let owner = ctx.visibility.inherited_injected_class_owner(
4194        &ctx.analyzer,
4195        ctx.file,
4196        &enclosing_owner,
4197        injected_name,
4198    )?;
4199    same_visible_symbol(&owner, &ctx.spec.target)
4200        .then(|| qualified.nodes.first().copied())
4201        .flatten()
4202}
4203
4204/// Resolve each qualified type component against the inverse target while
4205/// preserving C++ lexical-tier precedence and structured alias identity.
4206fn target_guided_qualifier_type_scopes<'tree>(
4207    node: Node<'tree>,
4208    ctx: &ScanCtx<'_>,
4209) -> Option<Vec<Node<'tree>>> {
4210    if !matches!(
4211        node.kind(),
4212        "qualified_identifier" | "scoped_type_identifier"
4213    ) {
4214        return None;
4215    }
4216    let target = physically_visible_type_target(ctx)?;
4217    let qualified = qualified_owner_components(node, ctx.source)?;
4218    // Prefer the C++ lexical tier that exactly matches a candidate's indexed
4219    // scope before falling back to suffix recovery.  A short unqualified
4220    // owner can have a same-spelled class in a nested namespace (for example
4221    // `ThreadDetails` and `Ui::ThreadDetails`).  Suffix-only matching treats
4222    // both as possible owners and then fails closed, even though the
4223    // translation unit's lexical scope selects the global class.  Keep the
4224    // suffix path for malformed namespace sentinels, where the parser does
4225    // not expose every indexed scope component.
4226    let lexical_scope = match enclosing_lexical_scope_components(
4227        node,
4228        &ctx.analyzer,
4229        ctx.visibility,
4230        ctx.file,
4231        ctx.source,
4232    ) {
4233        LexicalScopeResolution::Resolved(scope) => scope,
4234        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4235            enclosing_namespace_components(node, ctx.source)
4236        }
4237    };
4238    let indexed_owner_scope =
4239        indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4240    let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4241    let mut matches = Vec::new();
4242    for component_count in 1..=qualified.names.len() {
4243        let components = &qualified.names[..component_count];
4244        let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4245            .collect::<Vec<_>>();
4246        let name = components.last()?;
4247        let mut candidates = Vec::new();
4248        let mut exact_candidates = Vec::new();
4249        for candidate in ctx
4250            .visibility
4251            .visible_identifier_candidates(ctx.file, name)
4252            .filter(|candidate| candidate.is_class())
4253            .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4254        {
4255            let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4256                brokk_bifrost_core::analyzer::Language::Cpp,
4257                &cpp_name_for(candidate),
4258            );
4259            if !candidate_components.ends_with(components)
4260                || candidates
4261                    .iter()
4262                    .any(|existing| same_logical_symbol(existing, candidate))
4263            {
4264                continue;
4265            }
4266            let exact_lexical_scope = lexical_tiers
4267                .iter()
4268                .any(|expected| expected == &candidate_components);
4269            let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4270            let structured_owner_match = indexed_owner_scope
4271                .as_ref()
4272                .is_some_and(|owner| owner.starts_with(candidate_owner))
4273                || recovered_owner_scope
4274                    .as_ref()
4275                    .is_some_and(|owner| owner.starts_with(candidate_owner));
4276            let class_alias_owner_match = ctx
4277                .analyzer
4278                .type_alias_provider()
4279                .is_some_and(|provider| provider.is_type_alias(candidate))
4280                && member_alias_owner_matches_reference_for(candidate, node, ctx);
4281            let macro_namespace_owner_match = is_declaration_name(node)
4282                && macro_namespace_scope_matches(candidate_owner, node, ctx);
4283            if components.len() == 1
4284                && candidate_components != components
4285                && !exact_lexical_scope
4286                && !structured_owner_match
4287                && !class_alias_owner_match
4288                && !macro_namespace_owner_match
4289            {
4290                continue;
4291            }
4292            candidates.push(candidate.clone());
4293            if exact_lexical_scope {
4294                exact_candidates.push(candidate.clone());
4295            }
4296        }
4297        if !exact_candidates.is_empty() {
4298            candidates = exact_candidates;
4299        }
4300        // A typedef spelling can qualify nested C++ members while forward
4301        // lookup canonicalizes that spelling to its underlying class. Preserve
4302        // the exact alias prefix only when structured alias resolution proves
4303        // that it denotes this inverse target.
4304        let canonical_alias_target_matches = matches!(
4305            candidates.as_slice(),
4306            [candidate]
4307                if ctx
4308                    .analyzer
4309                    .type_alias_provider()
4310                    .is_some_and(|provider| provider.is_type_alias(candidate))
4311                    && type_candidate_visible_at_reference(candidate, node, ctx)
4312                    && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4313                    && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4314                        brokk_bifrost_core::analyzer::Language::Cpp,
4315                        &cpp_name_for(candidate),
4316                    ) == components
4317                        || member_alias_owner_matches_reference_for(candidate, node, ctx))
4318        );
4319        let direct_alias_target = ctx
4320            .analyzer
4321            .type_alias_provider()
4322            .is_some_and(|provider| provider.is_type_alias(target))
4323            && candidates
4324                .iter()
4325                .any(|candidate| same_symbol(candidate, target));
4326        let unique_target = matches!(
4327            candidates.as_slice(),
4328            [candidate] if same_visible_symbol(candidate, target)
4329        );
4330        if direct_alias_target || unique_target || canonical_alias_target_matches {
4331            let matched = if ctx
4332                .analyzer
4333                .type_alias_provider()
4334                .is_some_and(|provider| provider.is_type_alias(target))
4335                && ctx
4336                    .analyzer
4337                    .parent_of(target)
4338                    .is_some_and(|owner| owner.is_class())
4339                && ctx
4340                    .visibility
4341                    .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4342                && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4343            {
4344                // The alias declaration owns the terminal component. Keep
4345                // the inverse range narrow so `MathLib::bigint` records the
4346                // `bigint` token, not the complete qualified owner path.
4347                qualified.nodes[component_count - 1]
4348            } else {
4349                qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4350            };
4351            if template_type_component_preserves_target(matched, &candidates, ctx) {
4352                matches.push(matched);
4353            }
4354        }
4355    }
4356    (!matches.is_empty()).then_some(matches)
4357}
4358
4359fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4360    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4361        return false;
4362    };
4363    ctx.visibility
4364        .visible_identifier_candidates(ctx.file, target.identifier())
4365        .any(|candidate| {
4366            alias_provider.is_type_alias(candidate)
4367                && candidate.identifier() == target.identifier()
4368                && !same_visible_symbol(candidate, target)
4369                && ctx
4370                    .analyzer
4371                    .parent_of(candidate)
4372                    .is_some_and(|owner| owner.is_class())
4373        })
4374}
4375
4376/// Recover an out-of-line owner when the owner declaration and the reference
4377/// use different unknown preprocessor guards. Keep this result unproven.
4378fn target_guided_unproven_out_of_line_owner<'tree>(
4379    node: Node<'tree>,
4380    ctx: &ScanCtx<'_>,
4381) -> Option<Node<'tree>> {
4382    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4383        || !is_declaration_name(node)
4384    {
4385        return None;
4386    }
4387    let target = physically_visible_type_target(ctx)?;
4388    if !target.is_class() {
4389        return None;
4390    }
4391    let qualified = qualified_owner_components(node, ctx.source)?;
4392    let target_components = canonical_cpp_scope_components(target);
4393    let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4394    let parser_scope = enclosing_namespace_components(node, ctx.source);
4395    let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4396        if !parser_scope.is_empty() || target_namespace.is_empty() {
4397            Some(parser_scope)
4398        } else {
4399            indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4400        }
4401    })?;
4402    if has_malformed_wrapper_function_definition_ancestor(node)
4403        && target_namespace.starts_with(&scope)
4404        && target_namespace.len() > scope.len()
4405    {
4406        scope = target_namespace.to_vec();
4407    }
4408    if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4409        .any(|components| components == target_components)
4410    {
4411        return None;
4412    }
4413    let owner_name = qualified.names.last()?;
4414    let candidates = ctx
4415        .visibility
4416        .visible_identifier_candidates(ctx.file, owner_name)
4417        .filter(|candidate| {
4418            candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4419        })
4420        .collect::<Vec<_>>();
4421    if candidates.is_empty()
4422        || candidates
4423            .iter()
4424            .any(|candidate| !same_visible_symbol(candidate, target))
4425    {
4426        return None;
4427    }
4428    qualified.nodes.last().copied()
4429}
4430
4431fn macro_namespace_scope_matches(
4432    candidate_owner: &[String],
4433    node: Node<'_>,
4434    ctx: &ScanCtx<'_>,
4435) -> bool {
4436    let namespace = enclosing_namespace_components(node, ctx.source);
4437    if namespace.is_empty() || candidate_owner.is_empty() {
4438        return false;
4439    }
4440    let mut expanded_owner = Vec::new();
4441    for component in candidate_owner {
4442        if let Some(replacement) =
4443            ctx.visibility
4444                .object_macro_replacement_at(ctx.file, component, node.start_byte())
4445        {
4446            let replacement_components =
4447                brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4448                    brokk_bifrost_core::analyzer::Language::Cpp,
4449                    &replacement,
4450                );
4451            if replacement_components.is_empty() {
4452                return false;
4453            }
4454            expanded_owner.extend(replacement_components);
4455        } else {
4456            expanded_owner.push(component.clone());
4457        }
4458    }
4459    expanded_owner == namespace
4460}
4461
4462fn target_guided_missing_type_leaf<'tree>(
4463    node: Node<'tree>,
4464    ctx: &ScanCtx<'_>,
4465) -> Option<Node<'tree>> {
4466    physically_visible_type_target(ctx)?;
4467    target_guided_missing_dependent_nested_type_leaf(node, ctx)
4468        .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
4469        .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
4470        .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
4471        .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
4472        .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4473        .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
4474}
4475
4476/// Recover a bare class-owned alias whose structured canonical target is the
4477/// requested type. The class owner must enclose the reference, and every alias
4478/// with that spelling in the owner chain must preserve the same target.
4479fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4480    node: Node<'tree>,
4481    ctx: &ScanCtx<'_>,
4482) -> Option<Node<'tree>> {
4483    if node.kind() != "type_identifier"
4484        || is_declaration_name(node)
4485        || local_type_name_shadows(node, ctx)
4486    {
4487        return None;
4488    }
4489    let alias_provider = ctx.analyzer.type_alias_provider()?;
4490    let name = node_text(node, ctx.source);
4491    let aliases = ctx
4492        .visibility
4493        .visible_identifier_candidates(ctx.file, name)
4494        .filter(|candidate| alias_provider.is_type_alias(candidate))
4495        .filter(|candidate| {
4496            ctx.analyzer
4497                .parent_of(candidate)
4498                .is_some_and(|owner| owner.is_class())
4499        })
4500        .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4501        .filter(|candidate| {
4502            ctx.visibility
4503                .external_type_candidate_guard_compatible_in_context(
4504                    &ctx.analyzer,
4505                    ctx.file,
4506                    candidate,
4507                    node,
4508                )
4509        })
4510        .collect::<Vec<_>>();
4511    (!aliases.is_empty()
4512        && aliases.iter().all(|candidate| {
4513            same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4514        }))
4515    .then_some(node)
4516}
4517
4518/// Recover an ambiguous unqualified alias used by a parameter or placement-new
4519/// type only when the indexed class owner proves the alias declaration. This
4520/// narrow path covers malformed class bodies without accepting unrelated aliases.
4521fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
4522    node: Node<'tree>,
4523    ctx: &ScanCtx<'_>,
4524) -> Option<Node<'tree>> {
4525    let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
4526        matches!(
4527            declaration.kind(),
4528            "parameter_declaration" | "optional_parameter_declaration"
4529        )
4530    });
4531    let placement_new_type = node.parent().is_some_and(|parent| {
4532        parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
4533    });
4534    if !parameter && !placement_new_type {
4535        return None;
4536    }
4537    if !ctx
4538        .analyzer
4539        .type_alias_provider()
4540        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4541        || !type_alias_owner_matches_structured_reference(node, ctx)
4542    {
4543        return None;
4544    }
4545    target_guided_missing_declaration_type_leaf(node, ctx)
4546}
4547
4548/// Recover the terminal leaf of `Owner<T>::Nested` when a malformed namespace
4549/// sentinel prevents ordinary lexical resolution. The indexed target must have
4550/// an indexed class parent, the structured owner path must compose with one
4551/// proven lexical namespace source, and every visible candidate at that exact
4552/// owner path must be the indexed parent. This keeps the fallback owner-based;
4553/// a same-spelled nested type under another template remains unproven.
4554fn target_guided_missing_dependent_nested_type_leaf<'tree>(
4555    node: Node<'tree>,
4556    ctx: &ScanCtx<'_>,
4557) -> Option<Node<'tree>> {
4558    if !matches!(
4559        node.kind(),
4560        "qualified_identifier" | "scoped_type_identifier"
4561    ) || !qualified_type_scope_contains_template(node)
4562    {
4563        return None;
4564    }
4565    let name = node
4566        .child_by_field_name("name")
4567        .filter(|name| name.kind() == "type_identifier")?;
4568    if node_text(name, ctx.source) != ctx.spec.target.identifier() {
4569        return None;
4570    }
4571    let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
4572    if !owner_target.is_class() {
4573        return None;
4574    }
4575    let owner = node.child_by_field_name("scope")?;
4576    let owner_resolution = resolve_type_node_lexically_for_target(
4577        owner,
4578        &ctx.analyzer,
4579        ctx.visibility,
4580        &ctx.ordinary_type_imports,
4581        ctx.file,
4582        ctx.source,
4583        &owner_target,
4584        Some(&ctx.lexical_scope_cache),
4585        ctx.recovered_sentinel_scope(owner).as_deref(),
4586    );
4587    if matches!(
4588        owner_resolution,
4589        LexicalTypeResolution::Resolved {
4590            ref unit,
4591            ref candidates,
4592            ..
4593        } if type_resolution_matches_unit_target(
4594            owner,
4595            unit,
4596            candidates,
4597            &owner_target,
4598            ctx,
4599        )
4600    ) {
4601        return Some(name);
4602    }
4603
4604    let qualified = qualified_owner_components(node, ctx.source)?;
4605    let parser_namespace = enclosing_namespace_components(node, ctx.source);
4606    let orphaned_namespace = ctx
4607        .orphaned_namespaces
4608        .iter()
4609        .filter(|envelope| envelope.body_end < node.start_byte())
4610        .max_by_key(|envelope| envelope.body_end)
4611        .map(|envelope| envelope.components.clone());
4612    let indexed_scope = ctx
4613        .recovered_sentinel_scope(node)
4614        .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
4615        .or(orphaned_namespace)
4616        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
4617    let owner_components = canonical_cpp_scope_components(&owner_target);
4618    if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
4619        .any(|components| components == owner_components)
4620    {
4621        return None;
4622    }
4623    let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
4624        .into_iter()
4625        .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
4626        .collect::<Vec<_>>();
4627    (!scoped_candidates.is_empty()
4628        && scoped_candidates
4629            .iter()
4630            .all(|candidate| same_visible_symbol(candidate, &owner_target)))
4631    .then_some(name)
4632}
4633
4634/// Recover a type leaf after tree-sitter has prematurely closed a malformed
4635/// namespace around a preprocessor construct. The parser-derived lexical scope
4636/// is empty (or stops at an outer namespace), while the indexed target and a
4637/// syntax-error namespace envelope still provide a structured owner boundary.
4638///
4639/// This is deliberately narrower than a general target-name fallback:
4640/// - only direct class/enum leaves are eligible (aliases use their own paths);
4641/// - the reference must be after an error-marked namespace envelope whose full
4642///   namespace path equals the target package;
4643/// - same-file targets must have a declaration range inside that envelope; and
4644/// - ordinary leaves retain a unanimous guard among candidates in that exact
4645///   recovered namespace tier; a macro-displaced declarator type may instead
4646///   use the unmatched namespace close as an exact physical upper bound.
4647///
4648/// These checks keep an unqualified same-spelled type in another namespace from
4649/// becoming a false positive merely because an earlier namespace was malformed.
4650fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
4651    node: Node<'tree>,
4652    ctx: &ScanCtx<'_>,
4653) -> Option<Node<'tree>> {
4654    let target = physically_visible_type_target(ctx)?;
4655    let leaf = template_reference_name_node(node).unwrap_or(node);
4656    let name = node_text(leaf, ctx.source);
4657    let (components, global) = type_reference_components(node, ctx.source)?;
4658    if global || components.len() != 1 || components[0] != name {
4659        return None;
4660    }
4661    if !target.is_class()
4662        || ctx
4663            .analyzer
4664            .type_alias_provider()
4665            .is_some_and(|provider| provider.is_type_alias(target))
4666        || is_declaration_name(leaf)
4667        || name != target.identifier()
4668        || ctx.local_shadows.is_shadowed(name)
4669        || local_type_name_shadows(leaf, ctx)
4670        || !ctx.visibility.is_physically_visible(ctx.file, target)
4671        || !ctx.visibility.external_type_candidate_visible_in_context(
4672            &ctx.analyzer,
4673            ctx.file,
4674            target,
4675            leaf,
4676        )
4677    {
4678        return None;
4679    }
4680
4681    let target_components = canonical_cpp_scope_components(target);
4682    let target_package_len = target_components.len().checked_sub(1)?;
4683    let target_package = &target_components[..target_package_len];
4684    let surviving_namespace = enclosing_namespace_components(leaf, ctx.source);
4685    if !target_package.starts_with(&surviving_namespace) {
4686        return None;
4687    }
4688    let bounded_macro_type = recovered_macro_decorated_declarator_type(leaf).is_some();
4689    let (body_end, namespace_components) = if bounded_macro_type {
4690        let mut root = leaf;
4691        while let Some(parent) = root.parent() {
4692            root = parent;
4693        }
4694        ctx.orphaned_namespace_scopes
4695            .get_or_init(|| OrphanedNamespaceTypeScopeIndex::build(root, ctx.source))
4696            .scope_at(leaf.start_byte())?
4697    } else {
4698        let envelope = ctx
4699            .orphaned_namespaces
4700            .iter()
4701            .filter(|envelope| {
4702                envelope.body_end < leaf.start_byte()
4703                    && envelope.components.as_slice() == target_package
4704            })
4705            .max_by_key(|envelope| envelope.body_end)?;
4706        (envelope.body_end, envelope.components.as_slice())
4707    };
4708    if namespace_components != target_package {
4709        return None;
4710    }
4711
4712    if target.source() == ctx.file
4713        && !ctx.target_declaration_ranges.iter().any(|range| {
4714            range.start_byte < body_end
4715                    // A recovered class range includes its trailing `;`, while
4716                    // the prematurely closed namespace body ends after `}`.
4717                    && range.end_byte <= body_end.saturating_add(1)
4718        })
4719    {
4720        return None;
4721    }
4722
4723    // The recovered namespace envelope proves the target's exact lexical tier,
4724    // while the parser namespace still identifies declarations that compete at
4725    // the physical reference site. A same-terminal declaration in a sibling
4726    // namespace belongs to neither tier and cannot veto the recovered target.
4727    let candidates = visible_type_identifier_candidates(ctx, name)
4728        .into_iter()
4729        .filter(|candidate| {
4730            let components = canonical_cpp_scope_components(candidate);
4731            components == target_components
4732                || components
4733                    .get(..components.len().saturating_sub(1))
4734                    .is_some_and(|package| package == surviving_namespace)
4735        })
4736        .collect::<Vec<_>>();
4737    let target_is_visible = candidates
4738        .iter()
4739        .any(|candidate| same_visible_symbol(candidate, target));
4740    let ordinary_candidates_are_unanimous = candidates
4741        .iter()
4742        .all(|candidate| same_visible_symbol(candidate, target));
4743    if !target_is_visible || (!bounded_macro_type && !ordinary_candidates_are_unanimous) {
4744        return None;
4745    }
4746    Some(leaf)
4747}
4748
4749/// Retry a bare alias after tree-sitter has prematurely closed a malformed
4750/// namespace. The error-marked namespace envelope supplies only the missing
4751/// lexical scope; the ordinary structured resolver must still select an
4752/// indexed alias and prove that its canonical type is the inverse target.
4753fn orphaned_namespace_alias_type_resolution(
4754    node: Node<'_>,
4755    ctx: &ScanCtx<'_>,
4756) -> Option<LexicalTypeResolution> {
4757    let (reference_components, global) = type_reference_components(node, ctx.source)?;
4758    if global
4759        || reference_components.len() != 1
4760        || is_declaration_name(node)
4761        || local_type_name_shadows(node, ctx)
4762    {
4763        return None;
4764    }
4765
4766    let surviving_namespace = enclosing_namespace_components(node, ctx.source);
4767    let envelope = ctx
4768        .orphaned_namespaces
4769        .iter()
4770        .filter(|envelope| {
4771            envelope.body_end < node.start_byte()
4772                && envelope.components.starts_with(&surviving_namespace)
4773        })
4774        .max_by_key(|envelope| envelope.body_end)?;
4775    let resolution = resolve_type_node_lexically_for_target(
4776        node,
4777        &ctx.analyzer,
4778        ctx.visibility,
4779        &ctx.ordinary_type_imports,
4780        ctx.file,
4781        ctx.source,
4782        &ctx.spec.target,
4783        Some(&ctx.lexical_scope_cache),
4784        Some(&envelope.components),
4785    );
4786    let LexicalTypeResolution::Resolved {
4787        ref unit,
4788        ref components,
4789        ref candidates,
4790    } = resolution
4791    else {
4792        return None;
4793    };
4794    let alias_provider = ctx.analyzer.type_alias_provider()?;
4795    if components.len() != envelope.components.len() + 1
4796        || !components.starts_with(&envelope.components)
4797        || candidates.is_empty()
4798        || candidates.iter().any(|candidate| {
4799            !alias_provider.is_type_alias(candidate)
4800                || canonical_cpp_scope_components(candidate) != *components
4801        })
4802        || !type_resolution_matches_target(node, unit, candidates, ctx)
4803    {
4804        return None;
4805    }
4806    Some(resolution)
4807}
4808
4809/// Recover a nested type-alias reference when parser recovery leaves an
4810/// unqualified template argument under a member function.  The ordinary
4811/// lexical lookup can select a same-spelled namespace alias (or fail closed)
4812/// even though the indexed callable owner proves that the reference is inside
4813/// the class which declares the target alias.
4814fn target_guided_missing_member_alias_type_leaf<'tree>(
4815    node: Node<'tree>,
4816    ctx: &ScanCtx<'_>,
4817) -> Option<Node<'tree>> {
4818    if !is_cpp_template_argument_type_leaf(node)
4819        || is_declaration_name(node)
4820        || ctx
4821            .target_declaration_ranges
4822            .iter()
4823            .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
4824        || node_text(node, ctx.source) != ctx.spec.target.identifier()
4825        || local_type_name_shadows(node, ctx)
4826        || !ctx
4827            .analyzer
4828            .type_alias_provider()
4829            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4830    {
4831        return None;
4832    }
4833    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
4834    let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
4835    if !owner_scope_matches
4836        && !indexed_scope.is_some_and(|scope| {
4837            indexed_scope_matches_target_name(
4838                &scope,
4839                &[ctx.spec.target.identifier().to_string()],
4840                false,
4841                &ctx.spec.target,
4842            )
4843        })
4844    {
4845        return None;
4846    }
4847    // The indexed symbol table intentionally retains declarations from every
4848    // preprocessor branch and from later source positions.  The recovered
4849    // class-owner scope proves the spelling, but it does not prove that this
4850    // alias was active and introduced before the reference.  Apply the same
4851    // structured guard/source-order check used by the ordinary resolver before
4852    // turning the target-guided recovery into a proven hit.
4853    if !(ctx.visibility.external_type_candidate_visible_in_context(
4854        &ctx.analyzer,
4855        ctx.file,
4856        &ctx.spec.target,
4857        node,
4858    ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
4859    {
4860        return None;
4861    }
4862    let target_visible = ctx
4863        .visibility
4864        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4865        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4866    target_visible.then_some(node)
4867}
4868
4869/// Recover a class/enum template argument only when the parser's ordinary
4870/// lexical lookup failed but the indexed scope and visible declaration still
4871/// prove the exact target. This intentionally excludes aliases: an alias
4872/// argument needs its own template-argument selection path, while a direct
4873/// class/enum argument can be identified by its canonical scope and symbol.
4874fn target_guided_missing_template_argument_type_leaf<'tree>(
4875    node: Node<'tree>,
4876    ctx: &ScanCtx<'_>,
4877) -> Option<Node<'tree>> {
4878    let target = &ctx.spec.target;
4879    let name = node_text(node, ctx.source);
4880    if !target.is_class()
4881        || !is_cpp_template_argument_type_leaf(node)
4882        || is_declaration_name(node)
4883        || name != target.identifier()
4884        || ctx.local_shadows.is_shadowed(name)
4885        || local_type_name_shadows(node, ctx)
4886        || !ctx.visibility.is_physically_visible(ctx.file, target)
4887        || ctx
4888            .analyzer
4889            .type_alias_provider()
4890            .is_some_and(|provider| provider.is_type_alias(target))
4891    {
4892        return None;
4893    }
4894
4895    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4896    let target_components = canonical_cpp_scope_components(target);
4897    if target_components.last().map(String::as_str) != Some(name)
4898        || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4899            .any(|components| components == target_components)
4900    {
4901        return None;
4902    }
4903
4904    // The direct visible class candidate supplies the declaration identity;
4905    // the scope check above supplies its canonical owner path. Do not let an
4906    // alias or a same-scoped competing class enter this recovery path.
4907    let candidates = visible_type_identifier_candidates(ctx, name);
4908    if candidates.is_empty()
4909        || candidates.iter().any(|candidate| {
4910            !candidate.is_class()
4911                || ctx
4912                    .analyzer
4913                    .type_alias_provider()
4914                    .is_some_and(|provider| provider.is_type_alias(candidate))
4915                || (!same_visible_symbol(candidate, target)
4916                    && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4917                        .any(|components| components == canonical_cpp_scope_components(candidate)))
4918        })
4919        || !candidates
4920            .iter()
4921            .any(|candidate| same_visible_symbol(candidate, target))
4922    {
4923        return None;
4924    }
4925
4926    // Physical visibility covers the file/import projection; this second
4927    // guard preserves declaration ordering and preprocessor branch identity.
4928    ctx.visibility
4929        .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
4930        .then_some(node)
4931}
4932
4933/// Recover the class owner of an out-of-line member whose trailing attribute
4934/// macro was parsed as a separate function definition around the real body.
4935fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
4936    let mut function = node;
4937    while function.kind() != "function_definition" {
4938        function = function.parent()?;
4939    }
4940    let macro_name = function_definition_name_node(function)?;
4941    if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
4942        return None;
4943    }
4944
4945    // An unknown trailing attribute macro can split one real definition into
4946    // a missing-semicolon declaration for `Owner::method()` and an adjacent
4947    // macro-named function definition that owns the body. Recover only that
4948    // exact CST sequence; a complete declaration or a non-macro function is
4949    // an ordinary independent construct.
4950    let declaration = function.prev_named_sibling()?;
4951    if declaration.kind() != "declaration"
4952        || !declaration.has_error()
4953        || function.start_position().row > declaration.end_position().row + 1
4954    {
4955        return None;
4956    }
4957    let mut missing_semicolon = false;
4958    let mut real_semicolon = false;
4959    for index in 0..declaration.child_count() {
4960        let Some(child) = declaration.child(index) else {
4961            continue;
4962        };
4963        if child.kind() == ";" {
4964            missing_semicolon |= child.is_missing();
4965            real_semicolon |= !child.is_missing();
4966        }
4967    }
4968    if !missing_semicolon || real_semicolon {
4969        return None;
4970    }
4971
4972    let initializer = declaration.child_by_field_name("declarator")?;
4973    if initializer.kind() != "init_declarator"
4974        || initializer
4975            .child_by_field_name("value")
4976            .is_none_or(|value| value.kind() != "argument_list")
4977    {
4978        return None;
4979    }
4980    let qualified_name = initializer
4981        .child_by_field_name("declarator")
4982        .and_then(declarator_name_node)?;
4983    let qualified = qualified_owner_components(qualified_name, ctx.source)?;
4984    let lexical_scope = enclosing_namespace_components(function, ctx.source);
4985    match ctx.visibility.resolve_type_components_lexically(
4986        &ctx.analyzer,
4987        ctx.file,
4988        &qualified.names,
4989        qualified.global,
4990        &lexical_scope,
4991    ) {
4992        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
4993        LexicalTypeResolution::Resolved { .. }
4994        | LexicalTypeResolution::Ambiguous
4995        | LexicalTypeResolution::Missing => None,
4996    }
4997}
4998
4999/// A class member alias is visible throughout its complete class scope, even
5000/// when its declaration byte follows a recovered out-of-line member's
5001/// trailing return type. Match the indexed owner path structurally before
5002/// allowing the guard-only visibility check above to waive source ordering.
5003fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5004    member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5005}
5006
5007fn member_alias_owner_matches_reference_for(
5008    target: &CodeUnit,
5009    node: Node<'_>,
5010    ctx: &ScanCtx<'_>,
5011) -> bool {
5012    let Some(owner) = ctx.analyzer.parent_of(target) else {
5013        return false;
5014    };
5015    if !owner.is_class() {
5016        return false;
5017    }
5018    let reference_owner = ctx
5019        .class_ranges
5020        .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5021        .or_else(|| structured_enclosing_owner(node, ctx));
5022    if reference_owner.as_ref().is_some_and(|reference_owner| {
5023        ctx.visibility
5024            .same_template_owner_identity(&owner, reference_owner)
5025    }) {
5026        return true;
5027    }
5028    if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5029        ctx.visibility
5030            .same_template_owner_identity(&owner, &reference_owner)
5031    }) {
5032        return true;
5033    }
5034    if reference_owner.is_some_and(|reference_owner| {
5035        matches!(
5036            resolve_declaring_member_owner(
5037                &ctx.analyzer,
5038                ctx.visibility,
5039                ctx.file,
5040                &reference_owner,
5041                target.identifier(),
5042            ),
5043            EnclosingMemberOwnerResolution::Owner(declaring_owner)
5044                if ctx
5045                    .visibility
5046                    .same_template_owner_identity(&owner, &declaring_owner)
5047        )
5048    }) {
5049        return true;
5050    }
5051    let range = Range {
5052        start_byte: node.start_byte(),
5053        end_byte: node.end_byte(),
5054        start_line: node.start_position().row + 1,
5055        end_line: node.end_position().row + 1,
5056    };
5057    let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5058    while let Some(candidate) = indexed_enclosing {
5059        if candidate.is_class()
5060            && ctx
5061                .visibility
5062                .same_template_owner_identity(&owner, &candidate)
5063        {
5064            return true;
5065        }
5066        indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5067    }
5068    if let Some(reference_body) = malformed_recovered_class_body(node) {
5069        let mut root = node;
5070        while let Some(parent) = root.parent() {
5071            root = parent;
5072        }
5073        if ctx.analyzer.ranges(target).iter().any(|range| {
5074            root.descendant_for_byte_range(range.start_byte, range.end_byte)
5075                .and_then(malformed_recovered_class_body)
5076                .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5077        }) {
5078            return true;
5079        }
5080    }
5081    if structured_enclosing_owner(node, ctx)
5082        .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5083    {
5084        return true;
5085    }
5086    let owner_components = canonical_cpp_scope_components(&owner);
5087    if ctx
5088        .recovered_sentinel_scope(node)
5089        .is_some_and(|scope| scope == owner_components)
5090    {
5091        return true;
5092    }
5093    if matches!(
5094        cached_enclosing_lexical_scope_components_with_unresolved_owner(
5095            node,
5096            &ctx.analyzer,
5097            ctx.visibility,
5098            ctx.file,
5099            ctx.source,
5100            false,
5101            false,
5102            Some(&ctx.lexical_scope_cache),
5103        ),
5104        LexicalScopeResolution::Resolved(reference_scope)
5105            if reference_scope == owner_components
5106    ) {
5107        return true;
5108    }
5109    let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5110    else {
5111        return false;
5112    };
5113    !owner_components.is_empty() && reference_scope == owner_components
5114}
5115
5116fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5117    loop {
5118        if node.kind() == "compound_statement"
5119            && node
5120                .parent()
5121                .is_some_and(|parent| parent.kind() == "declaration_list")
5122            && node.prev_named_sibling().is_some_and(|header| {
5123                header.kind() == "ERROR"
5124                    && header.end_byte() <= node.start_byte()
5125                    && error_contains_class_header(header)
5126            })
5127        {
5128            return Some(node);
5129        }
5130        node = node.parent()?;
5131    }
5132}
5133
5134fn error_contains_class_header(node: Node<'_>) -> bool {
5135    let mut pending = vec![(node, 0usize)];
5136    while let Some((current, depth)) = pending.pop() {
5137        if matches!(current.kind(), "class" | "struct" | "union") {
5138            let mut sibling = current.next_sibling();
5139            let mut saw_name = false;
5140            while let Some(candidate) = sibling {
5141                match candidate.kind() {
5142                    "comment" => {}
5143                    "{" | "base_class_clause" | ":" => return saw_name,
5144                    "identifier" | "type_identifier" if !saw_name => saw_name = true,
5145                    _ if !candidate.is_named() => {}
5146                    _ => break,
5147                }
5148                sibling = candidate.next_sibling();
5149            }
5150        }
5151        if depth >= 1 {
5152            continue;
5153        }
5154        let mut cursor = current.walk();
5155        pending.extend(
5156            current
5157                .children(&mut cursor)
5158                .filter(|child| child.kind() != "compound_statement")
5159                .map(|child| (child, depth + 1)),
5160        );
5161    }
5162    false
5163}
5164
5165fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5166    has_ancestor_kind(node, "compound_statement")
5167        && ctx
5168            .visibility
5169            .external_type_candidate_guard_compatible_in_context(
5170                &ctx.analyzer,
5171                ctx.file,
5172                &ctx.spec.target,
5173                node,
5174            )
5175}
5176
5177fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5178    ctx.analyzer
5179        .type_alias_provider()
5180        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5181        && member_alias_owner_matches_reference(node, ctx)
5182}
5183
5184/// A nested class can use aliases declared by any enclosing class. Preserve
5185/// that structured owner chain for malformed macro-return nodes, whose phantom
5186/// field spelling otherwise makes ordinary lexical lookup ambiguous.
5187fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5188    if !ctx
5189        .analyzer
5190        .type_alias_provider()
5191        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5192    {
5193        return false;
5194    }
5195    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5196        return false;
5197    };
5198    let mut reference_owner = structured_enclosing_owner(node, ctx);
5199    while let Some(owner) = reference_owner {
5200        if same_logical_symbol(&target_owner, &owner) {
5201            return true;
5202        }
5203        reference_owner = ctx.analyzer.parent_of(&owner);
5204    }
5205    false
5206}
5207
5208/// An enclosing class alias is only usable when no nearer class declares the
5209/// same type name. The recovered macro-return path does not have a complete
5210/// lexical declaration node, so ordinary lookup cannot apply this shadowing
5211/// rule before the enclosing alias fast path runs.
5212fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5213    let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5214        return false;
5215    };
5216    let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5217        return false;
5218    };
5219    let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5220        return false;
5221    };
5222    let candidates = ctx
5223        .visibility
5224        .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5225        .filter(|candidate| {
5226            candidate.is_class()
5227                && alias_provider.is_type_alias(candidate)
5228                && !same_visible_symbol(candidate, &ctx.spec.target)
5229        })
5230        .cloned()
5231        .collect::<Vec<_>>();
5232
5233    let mut owner = Some(reference_owner);
5234    while let Some(owner_unit) = owner {
5235        if same_logical_symbol(&target_owner, &owner_unit) {
5236            return false;
5237        }
5238        if candidates.iter().any(|candidate| {
5239            ctx.analyzer
5240                .parent_of(candidate)
5241                .is_some_and(|candidate_owner| {
5242                    candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5243                })
5244                && ctx
5245                    .visibility
5246                    .external_type_candidate_guard_compatible_in_context(
5247                        &ctx.analyzer,
5248                        ctx.file,
5249                        candidate,
5250                        node,
5251                    )
5252        }) {
5253            return true;
5254        }
5255        owner = ctx.analyzer.parent_of(&owner_unit);
5256    }
5257    false
5258}
5259
5260fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5261    // One question per node in the usage scan, which does not otherwise index
5262    // the tree; see `ParentIndex::unindexed`.
5263    if cpp_active_template_type_parameter(
5264        node,
5265        ctx.spec.target.identifier(),
5266        ctx.source,
5267        &ParentIndex::unindexed(),
5268    ) {
5269        return true;
5270    }
5271    let Some(callable) = nearest_callable_scope(node) else {
5272        return false;
5273    };
5274    let mut root_callable = callable;
5275    let mut ancestor = callable.parent();
5276    while let Some(current) = ancestor {
5277        if matches!(current.kind(), "function_definition" | "lambda_expression") {
5278            root_callable = current;
5279        }
5280        ancestor = current.parent();
5281    }
5282
5283    let mut stack = vec![root_callable];
5284    while let Some(current) = stack.pop() {
5285        if current.start_byte() >= node.start_byte() {
5286            continue;
5287        }
5288        if let Some(name) = local_type_name_declaration_node(current)
5289            && node_text(name, ctx.source) == ctx.spec.target.identifier()
5290            && nearest_callable_scope(current).is_some_and(|owner| {
5291                !is_malformed_wrapper_function_definition(owner)
5292                    && owner.start_byte() <= callable.start_byte()
5293                    && callable.end_byte() <= owner.end_byte()
5294            })
5295            && local_alias_scope_contains_node(current, node)
5296        {
5297            return true;
5298        }
5299        let mut cursor = current.walk();
5300        stack.extend(current.named_children(&mut cursor));
5301    }
5302    false
5303}
5304
5305fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5306    local_type_alias_name_node(node).or_else(|| match node.kind() {
5307        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5308            .child_by_field_name("name")
5309            .filter(|name| is_declaration_name(*name)),
5310        _ => None,
5311    })
5312}
5313
5314fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5315    loop {
5316        if matches!(node.kind(), "function_definition" | "lambda_expression") {
5317            return Some(node);
5318        }
5319        node = node.parent()?;
5320    }
5321}
5322
5323fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5324    match node.kind() {
5325        "alias_declaration" => node.child_by_field_name("name"),
5326        "type_definition" => node
5327            .child_by_field_name("declarator")
5328            .and_then(declarator_name_node),
5329        _ => None,
5330    }
5331}
5332
5333fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5334    let mut current = alias.parent();
5335    while let Some(parent) = current {
5336        if matches!(
5337            parent.kind(),
5338            "class_specifier" | "struct_specifier" | "union_specifier"
5339        ) {
5340            return false;
5341        }
5342        if parent.kind() == "compound_statement" {
5343            return parent.start_byte() <= node.start_byte()
5344                && node.end_byte() <= parent.end_byte();
5345        }
5346        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5347            let Some(body) = parent.child_by_field_name("body") else {
5348                return false;
5349            };
5350            return node_is_within(body, alias) && node_is_within(body, node);
5351        }
5352        current = parent.parent();
5353    }
5354    false
5355}
5356
5357fn target_guided_missing_declaration_type_leaf<'tree>(
5358    node: Node<'tree>,
5359    ctx: &ScanCtx<'_>,
5360) -> Option<Node<'tree>> {
5361    if is_declaration_name(node) {
5362        return None;
5363    }
5364    let component_nodes = cpp_name_component_nodes(node)?;
5365    let name_node = component_nodes.last().copied()?;
5366    let name = node_text(name_node, ctx.source);
5367    if name != ctx.spec.target.identifier() {
5368        return None;
5369    }
5370    let inside_target_declaration = ctx
5371        .target_declaration_ranges
5372        .iter()
5373        .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5374    if !inside_target_declaration
5375        && !ctx.visibility.external_type_candidate_visible_in_context(
5376            &ctx.analyzer,
5377            ctx.file,
5378            &ctx.spec.target,
5379            node,
5380        )
5381    {
5382        return None;
5383    }
5384    let components = component_nodes
5385        .iter()
5386        .map(|component| node_text(*component, ctx.source).to_string())
5387        .collect::<Vec<_>>();
5388    let local_alias_shadow = local_type_name_shadows(node, ctx);
5389    let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5390    let indexed_alias_owner = ctx
5391        .analyzer
5392        .type_alias_provider()
5393        .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5394        && member_alias_owner_matches_reference(node, ctx);
5395    let target_alias_self_reference = inside_target_declaration
5396        && ctx
5397            .analyzer
5398            .type_alias_provider()
5399            .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5400    let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5401        &ctx.analyzer,
5402        ctx.file,
5403        &ctx.spec.target,
5404        node,
5405    ) || member_alias_complete_class_context(node, ctx);
5406    if !target_alias_self_reference
5407        && !local_alias_shadow
5408        && member_alias_visible
5409        && (structured_alias_owner || indexed_alias_owner)
5410    {
5411        return Some(node);
5412    }
5413    let declaration = nearest_declaration_type_context(node)?;
5414    let candidates = visible_type_identifier_candidates(ctx, name);
5415    let unique_visible_target = !candidates.is_empty()
5416        && candidates
5417            .iter()
5418            .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5419    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5420    let exact_scope_match = indexed_scope_matches_target_name(
5421        &indexed_scope,
5422        &components,
5423        is_globally_qualified_cpp_name(node),
5424        &ctx.spec.target,
5425    );
5426    if matches!(declaration.kind(), "field_declaration" | "declaration") {
5427        let parser_lost_declaration_scope =
5428            target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5429                && unique_visible_target;
5430        return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5431    }
5432    let lost_namespace_parameter_context =
5433        matches!(
5434            declaration.kind(),
5435            "parameter_declaration" | "optional_parameter_declaration"
5436        ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5437    if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5438        return Some(node);
5439    }
5440    None
5441}
5442
5443fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5444    node: Node<'tree>,
5445    ctx: &ScanCtx<'_>,
5446) -> Option<Node<'tree>> {
5447    let mut stack = vec![node];
5448    while let Some(candidate) = stack.pop() {
5449        if candidate.kind() == "type_identifier"
5450            && !is_declaration_name(candidate)
5451            && matches!(
5452                candidate.parent().map(|parent| parent.kind()),
5453                Some("template_type")
5454            )
5455        {
5456            let mut current = candidate.parent();
5457            let mut saw_qualified = false;
5458            let mut saw_dependent = false;
5459            let mut saw_type_descriptor = false;
5460            let mut saw_alias_declaration = false;
5461            while let Some(ancestor) = current {
5462                match ancestor.kind() {
5463                    "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5464                    "dependent_type" => saw_dependent = true,
5465                    "type_descriptor" => saw_type_descriptor = true,
5466                    "alias_declaration" => {
5467                        saw_alias_declaration = true;
5468                        break;
5469                    }
5470                    "template_type"
5471                    | "template_argument_list"
5472                    | "typename"
5473                    | "template_declaration" => {}
5474                    _ => {}
5475                }
5476                current = ancestor.parent();
5477            }
5478            let name = node_text(candidate, ctx.source);
5479            let visible_candidates = visible_type_identifier_candidates(ctx, name);
5480            let canonical_alias_target = visible_candidates
5481                .iter()
5482                .filter_map(|alias| ctx.visibility.alias_target(alias))
5483                .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5484            let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
5485                &ctx.analyzer,
5486                ctx.file,
5487                name,
5488                &ctx.spec.target,
5489            ) || canonical_alias_target;
5490            if saw_qualified
5491                && saw_dependent
5492                && saw_type_descriptor
5493                && saw_alias_declaration
5494                && alias_resolves
5495                && ctx.visibility.external_type_candidate_visible_in_context(
5496                    &ctx.analyzer,
5497                    ctx.file,
5498                    &ctx.spec.target,
5499                    candidate,
5500                )
5501            {
5502                return Some(candidate);
5503            }
5504        }
5505        for index in (0..candidate.named_child_count()).rev() {
5506            if let Some(child) = candidate.named_child(index) {
5507                stack.push(child);
5508            }
5509        }
5510    }
5511    None
5512}
5513
5514fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5515    let mut current = Some(node);
5516    while let Some(ancestor) = current {
5517        if matches!(
5518            ancestor.kind(),
5519            "field_declaration"
5520                | "parameter_declaration"
5521                | "optional_parameter_declaration"
5522                | "declaration"
5523                | "type_descriptor"
5524        ) {
5525            let contains_type = ancestor
5526                .child_by_field_name("type")
5527                .is_some_and(|type_node| {
5528                    type_node.start_byte() <= node.start_byte()
5529                        && node.end_byte() <= type_node.end_byte()
5530                });
5531            if contains_type
5532                && !(ancestor.kind() == "type_descriptor"
5533                    && is_cpp_template_argument_type_leaf(node))
5534            {
5535                return Some(ancestor);
5536            }
5537            if ancestor.kind() == "type_descriptor"
5538                && ancestor.parent().is_some_and(|parent| {
5539                    matches!(
5540                        parent.kind(),
5541                        "cast_expression"
5542                            | "new_expression"
5543                            | "sizeof_expression"
5544                            | "alignof_expression"
5545                            | "typeid_expression"
5546                    )
5547                })
5548            {
5549                return Some(ancestor);
5550            }
5551        }
5552        if matches!(
5553            ancestor.kind(),
5554            "compound_statement"
5555                | "translation_unit"
5556                | "namespace_definition"
5557                | "alias_declaration"
5558                | "type_definition"
5559                | "base_class_clause"
5560        ) {
5561            return None;
5562        }
5563        current = ancestor.parent();
5564    }
5565    None
5566}
5567
5568fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5569    let mut candidates = Vec::new();
5570    for candidate in ctx
5571        .visibility
5572        .visible_identifier_candidates(ctx.file, name)
5573        .filter(|candidate| {
5574            candidate.is_class()
5575                || ctx
5576                    .analyzer
5577                    .type_alias_provider()
5578                    .is_some_and(|provider| provider.is_type_alias(candidate))
5579        })
5580    {
5581        if !candidates
5582            .iter()
5583            .any(|existing| same_logical_symbol(existing, candidate))
5584        {
5585            candidates.push(candidate.clone());
5586        }
5587    }
5588    candidates
5589}
5590
5591/// Recover a direct type-alias argument of `static_cast` when parser recovery
5592/// misclassifies a namespace alias as a local declaration. The indexed scope
5593/// and exact alias identity are required so a same-spelled alias in another
5594/// namespace remains excluded.
5595fn target_guided_static_cast_alias_type_descriptor<'tree>(
5596    node: Node<'tree>,
5597    ctx: &ScanCtx<'_>,
5598) -> Option<Node<'tree>> {
5599    if node.kind() != "type_descriptor" {
5600        return None;
5601    }
5602    let argument_list = node.parent().filter(|parent| {
5603        parent.kind() == "template_argument_list"
5604            && parent.named_child_count() == 1
5605            && parent.named_child(0) == Some(node)
5606    })?;
5607    let template = argument_list.parent().filter(|parent| {
5608        parent.kind() == "template_function"
5609            && parent.child_by_field_name("arguments") == Some(argument_list)
5610    })?;
5611    let name = template.child_by_field_name("name")?;
5612    if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5613        return None;
5614    }
5615    let target = &ctx.spec.target;
5616    if node_text(node, ctx.source) != target.identifier()
5617        || !ctx
5618            .analyzer
5619            .type_alias_provider()
5620            .is_some_and(|provider| provider.is_type_alias(target))
5621        || !ctx.visibility.is_physically_visible(ctx.file, target)
5622        || !ctx.visibility.external_type_candidate_visible_in_context(
5623            &ctx.analyzer,
5624            ctx.file,
5625            target,
5626            node,
5627        )
5628    {
5629        return None;
5630    }
5631
5632    let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5633    let target_scope = canonical_cpp_scope_components(target);
5634    let name_components = [target.identifier().to_string()];
5635    if !lexical_component_tiers(&name_components, false, &indexed_scope)
5636        .any(|components| components == target_scope)
5637    {
5638        return None;
5639    }
5640
5641    let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5642    if !candidates
5643        .iter()
5644        .any(|candidate| same_visible_symbol(candidate, target))
5645    {
5646        return None;
5647    }
5648    if candidates.iter().any(|candidate| {
5649        !same_visible_symbol(candidate, target)
5650            && lexical_component_tiers(&name_components, false, &indexed_scope)
5651                .any(|components| components == canonical_cpp_scope_components(candidate))
5652    }) {
5653        return None;
5654    }
5655    Some(node)
5656}
5657
5658fn indexed_scope_matches_target_name(
5659    indexed_scope: &[String],
5660    components: &[String],
5661    global: bool,
5662    target: &CodeUnit,
5663) -> bool {
5664    let target_name = cpp_name_for(target);
5665    lexical_component_tiers(components, global, indexed_scope)
5666        .any(|qualified| qualified.join("::") == target_name)
5667}
5668
5669fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5670    if target.package_name().is_empty() {
5671        return false;
5672    }
5673    if indexed_scope.len() <= 1 {
5674        return true;
5675    }
5676    let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5677        brokk_bifrost_core::analyzer::Language::Cpp,
5678        &cpp_name_for(target),
5679    );
5680    target_scope.pop();
5681    (1..indexed_scope.len())
5682        .rev()
5683        .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5684}
5685
5686fn indexed_enclosing_lexical_scope(
5687    analyzer: &CppGraphSource<'_>,
5688    file: &ProjectFile,
5689    node: Node<'_>,
5690) -> Option<Vec<String>> {
5691    let range = Range {
5692        start_byte: node.start_byte(),
5693        end_byte: node.end_byte(),
5694        start_line: node.start_position().row,
5695        end_line: node.end_position().row,
5696    };
5697    let enclosing = analyzer.enclosing_code_unit(file, &range)?;
5698    let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5699        brokk_bifrost_core::analyzer::Language::Cpp,
5700        &cpp_name_for(&enclosing),
5701    );
5702    if !enclosing.is_class() && !enclosing.is_module() {
5703        components.pop();
5704    }
5705    Some(components)
5706}
5707
5708fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
5709    if node.kind() != "qualified_identifier" {
5710        return None;
5711    }
5712    let mut stack = vec![node];
5713    while let Some(current) = stack.pop() {
5714        if current.kind() != "qualified_identifier" {
5715            continue;
5716        }
5717        if let Some(scope) = current.child_by_field_name("scope") {
5718            let text = qualified_scope_text(scope, ctx.source);
5719            if name_mentions(&text, &ctx.spec.member_name) {
5720                return Some(scope);
5721            }
5722        }
5723        let mut cursor = current.walk();
5724        for child in current.named_children(&mut cursor) {
5725            if child.kind() == "qualified_identifier" {
5726                stack.push(child);
5727            }
5728        }
5729    }
5730    None
5731}
5732
5733fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
5734    let mut parts = vec![node_text(scope, source).to_string()];
5735    let mut current = scope.parent();
5736    while let Some(qualified) = current {
5737        let Some(parent) = qualified.parent() else {
5738            break;
5739        };
5740        if parent.kind() != "qualified_identifier"
5741            || parent.child_by_field_name("name") != Some(qualified)
5742        {
5743            break;
5744        }
5745        if let Some(outer_scope) = parent.child_by_field_name("scope") {
5746            parts.push(node_text(outer_scope, source).to_string());
5747        }
5748        current = Some(parent);
5749    }
5750    parts.reverse();
5751    parts.join("::")
5752}
5753
5754fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5755    if node.kind() == "using_declaration" {
5756        maybe_record_using_callable_hit(node, ctx);
5757        return;
5758    }
5759    if has_ancestor_kind(node, "using_declaration") {
5760        return;
5761    }
5762    if node.kind() == "function_definition" {
5763        return;
5764    }
5765    if !matches!(
5766        node.kind(),
5767        "call_expression"
5768            | "new_expression"
5769            | "compound_literal_expression"
5770            | "declaration"
5771            | "field_initializer"
5772    ) {
5773        return;
5774    }
5775    let Some(owner) = ctx.spec.owner.as_ref() else {
5776        return;
5777    };
5778    if node.kind() == "field_initializer" {
5779        if !field_initializer_constructs_target(node, ctx, owner)
5780            && !unqualified_base_initializer_constructs_target(node, ctx, owner)
5781        {
5782            return;
5783        }
5784        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5785            match ctx
5786                .visibility
5787                .call_arity_evidence(ctx.file, node, ctx.source)
5788                .accepts(expected)
5789            {
5790                Some(true) => {}
5791                Some(false) => return,
5792                None => {
5793                    push_unproven_hit(node, ctx);
5794                    return;
5795                }
5796            }
5797        }
5798        match constructor_overload_selection(node, ctx) {
5799            ConstructorOverloadSelection::Target => push_hit(node, ctx),
5800            ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
5801            ConstructorOverloadSelection::OtherOverload => {}
5802        }
5803        return;
5804    }
5805    if node.kind() == "declaration" {
5806        if declaration_is_object_construction_candidate(node, ctx)
5807            && declaration_mentions_type(node, ctx, owner)
5808            && ctx
5809                .spec
5810                .callable_arity_at(node.start_byte())
5811                .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
5812        {
5813            match constructor_overload_selection(node, ctx) {
5814                ConstructorOverloadSelection::Target => push_hit(node, ctx),
5815                ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
5816                ConstructorOverloadSelection::OtherOverload => {}
5817            }
5818        }
5819        return;
5820    }
5821    let Some(type_node) = constructor_type_node(node) else {
5822        return;
5823    };
5824    let hit_node = function_terminal_node(type_node);
5825    let text = node_text(type_node, ctx.source);
5826    if !name_mentions(text, &ctx.spec.member_name) {
5827        return;
5828    }
5829    *ctx.raw_match_count += 1;
5830    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5831        match ctx
5832            .visibility
5833            .call_arity_evidence(ctx.file, node, ctx.source)
5834            .accepts(expected)
5835        {
5836            Some(true) => {}
5837            Some(false) => return,
5838            None => {
5839                push_unproven_hit(hit_node, ctx);
5840                return;
5841            }
5842        }
5843    }
5844    match constructor_overload_selection(node, ctx) {
5845        ConstructorOverloadSelection::Target => {}
5846        ConstructorOverloadSelection::Ambiguous => {
5847            push_unproven_hit(hit_node, ctx);
5848            return;
5849        }
5850        ConstructorOverloadSelection::OtherOverload => return,
5851    }
5852    let structured_resolution = resolve_type_node_lexically_for_target(
5853        type_node,
5854        &ctx.analyzer,
5855        ctx.visibility,
5856        &ctx.ordinary_type_imports,
5857        ctx.file,
5858        ctx.source,
5859        owner,
5860        Some(&ctx.lexical_scope_cache),
5861        ctx.recovered_sentinel_scope(type_node).as_deref(),
5862    );
5863    let structurally_resolves = matches!(
5864        &structured_resolution,
5865        LexicalTypeResolution::Resolved {
5866            unit, candidates, ..
5867        } if same_visible_symbol(unit, owner)
5868            || candidates
5869                .iter()
5870                .any(|candidate| same_visible_symbol(candidate, owner))
5871    );
5872    if structurally_resolves
5873        || matches!(structured_resolution, LexicalTypeResolution::Missing)
5874            && ctx
5875                .visibility
5876                .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
5877    {
5878        push_hit(hit_node, ctx);
5879    } else {
5880        push_unproven_hit(hit_node, ctx);
5881    }
5882}
5883
5884/// What the overload filter says about a construction site relative to the
5885/// constructor being scanned.
5886enum ConstructorOverloadSelection {
5887    /// The arguments leave the scan target as the only viable constructor, or
5888    /// the filter has no evidence to apply here. Either way nothing about the
5889    /// arguments argues against the site, so the rest of the scan decides it.
5890    Target,
5891    /// More than one of the owner's constructors stays viable, so the site
5892    /// cannot be proven to belong to the target.
5893    Ambiguous,
5894    /// The arguments select a sibling constructor and not the target.
5895    OtherOverload,
5896}
5897
5898/// Which of the owner's constructors the arguments at a construction site
5899/// select, for every shape `maybe_record_constructor_hit` accepts: `T(args)`,
5900/// `T{args}`, `new T(args)`, the member initializer `: field(args)`, and the
5901/// `T var(args)` declaration.
5902///
5903/// This is the inverse reading of the forward direction's overload choice. The
5904/// candidate set is the owner's arity-compatible constructors, the arguments are
5905/// typed by the same `expression_arg_type` the method and free-function scan
5906/// paths use, and `cpp_filter_candidates_by_args_with_parameter_types` applies
5907/// the same exact-then-conversion ranking (#2894). Selecting a constructor by
5908/// arity alone made a scan of one overload claim every same-arity sibling's call
5909/// (#2908).
5910fn constructor_overload_selection(
5911    node: Node<'_>,
5912    ctx: &ScanCtx<'_>,
5913) -> ConstructorOverloadSelection {
5914    let Some(owner) = ctx.spec.owner.as_ref() else {
5915        return ConstructorOverloadSelection::Target;
5916    };
5917    if ctx.spec.param_types.is_none() {
5918        return ConstructorOverloadSelection::Target;
5919    }
5920    // A `T var(args)` declaration keeps its arguments under the declarator or the
5921    // init declarator's value; every other shape spells an argument list that the
5922    // shared call helpers find, and its argument count needs the macro-aware
5923    // arity evidence.
5924    let (arity, arg_types) = if node.kind() == "declaration" {
5925        match declaration_constructor_initializer(node) {
5926            DeclarationConstructorInitializer::Arguments(arguments) => (
5927                argument_children(arguments).count(),
5928                argument_list_types(arguments, ctx),
5929            ),
5930            DeclarationConstructorInitializer::Expression(value) => {
5931                (1, vec![expression_arg_type(value, ctx)])
5932            }
5933            DeclarationConstructorInitializer::Empty => (0, Vec::new()),
5934        }
5935    } else {
5936        let Some(arity) = ctx
5937            .visibility
5938            .call_arity_evidence(ctx.file, node, ctx.source)
5939            .exact()
5940        else {
5941            return ConstructorOverloadSelection::Target;
5942        };
5943        (arity, call_argument_types(node, ctx))
5944    };
5945    let mut candidates = ctx
5946        .visibility
5947        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
5948        .into_iter()
5949        .filter(|unit| unit.is_function())
5950        .cloned()
5951        .collect::<Vec<_>>();
5952    candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5953    if !candidates
5954        .iter()
5955        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5956    {
5957        return ConstructorOverloadSelection::Target;
5958    }
5959    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
5960        candidates,
5961        &arg_types,
5962        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
5963        &|name| ctx.visibility.resolve_type(ctx.file, name),
5964        &|left, right| same_visible_symbol(left, right),
5965    );
5966    if !filtered
5967        .iter()
5968        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5969    {
5970        return ConstructorOverloadSelection::OtherOverload;
5971    }
5972    // A header declaration and its out-of-line body are one constructor even
5973    // when their persisted signature strings spell a parameter differently, so
5974    // `same_logical_callable` decides what counts as a surviving sibling (#2010).
5975    if filtered.iter().all(|candidate| {
5976        ctx.visibility
5977            .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
5978    }) {
5979        ConstructorOverloadSelection::Target
5980    } else {
5981        ConstructorOverloadSelection::Ambiguous
5982    }
5983}
5984
5985fn unqualified_base_initializer_constructs_target(
5986    node: Node<'_>,
5987    ctx: &ScanCtx<'_>,
5988    target_owner: &CodeUnit,
5989) -> bool {
5990    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
5991        return false;
5992    }
5993    let Some(name) = node
5994        .child_by_field_name("name")
5995        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
5996    else {
5997        return false;
5998    };
5999    if node_text(name, ctx.source) != ctx.spec.member_name {
6000        return false;
6001    }
6002    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6003        return false;
6004    };
6005    let inherited = ctx.visibility.inherited_injected_class_owner(
6006        &ctx.analyzer,
6007        ctx.file,
6008        &enclosing_owner,
6009        &ctx.spec.member_name,
6010    );
6011    inherited.is_some_and(|owner| {
6012        receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6013    })
6014}
6015
6016fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6017    if node.kind() == "function_definition" {
6018        maybe_record_free_function_definition_hit(node, ctx);
6019        return;
6020    }
6021    if node.kind() == "function_declarator" {
6022        maybe_record_recovered_error_free_function_call(node, ctx);
6023        return;
6024    }
6025    if node.kind() == "identifier" {
6026        maybe_record_free_function_value_reference(node, ctx);
6027        return;
6028    }
6029    if node.kind() != "call_expression" {
6030        return;
6031    }
6032    let Some(function) = node
6033        .child_by_field_name("function")
6034        .or_else(|| node.named_child(0))
6035    else {
6036        return;
6037    };
6038    let text = node_text(function, ctx.source);
6039    if !name_matches_callable(text, &ctx.spec.member_name) {
6040        return;
6041    }
6042    *ctx.raw_match_count += 1;
6043    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6044        match ctx
6045            .visibility
6046            .call_arity_evidence(ctx.file, node, ctx.source)
6047            .accepts(expected)
6048        {
6049            Some(true) => {}
6050            Some(false) => return,
6051            None => {
6052                // The argument count is unknown after macro expansion. It still
6053                // cannot select a *different* target when the bare name binds
6054                // to exactly one visible callable, so let the bare-call
6055                // resolution below prove that site; every other shape stays
6056                // unproven (#1811, the scan side of the same over-conservatism
6057                // that made the forward answer discard its lone candidate).
6058                if !bare_name_binds_only_target(node, function, text, ctx) {
6059                    push_unproven_hit(function_terminal_node(function), ctx);
6060                    return;
6061                }
6062            }
6063        }
6064    }
6065    if matches!(function.kind(), "identifier" | "template_function") {
6066        let terminal = function_terminal_node(function);
6067        let name = node_text(terminal, ctx.source);
6068        if ctx.local_shadows.is_shadowed(name) {
6069            return;
6070        }
6071        if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6072            && !matches!(
6073                resolve_declaring_member_owner(
6074                    &ctx.analyzer,
6075                    ctx.visibility,
6076                    ctx.file,
6077                    &enclosing_owner,
6078                    name,
6079                ),
6080                EnclosingMemberOwnerResolution::Missing
6081            )
6082        {
6083            return;
6084        }
6085        match resolve_bare_call_target(
6086            node,
6087            function,
6088            &ctx.analyzer,
6089            ctx.visibility,
6090            &ctx.ordinary_type_imports,
6091            ctx.file,
6092            ctx.source,
6093        ) {
6094            BareCallTargetResolution::FreeFunctions(units)
6095                if units
6096                    .iter()
6097                    .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
6098            {
6099                if free_function_call_may_target(node, text, ctx) {
6100                    let recursive = enclosing_context(terminal, ctx)
6101                        .enclosing
6102                        .as_ref()
6103                        .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6104                    if recursive {
6105                        push_recursive_reference_hit(terminal, ctx);
6106                    } else {
6107                        push_hit(terminal, ctx);
6108                    }
6109                }
6110            }
6111            BareCallTargetResolution::UnprovenFreeFunctions(units)
6112                if units
6113                    .iter()
6114                    .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
6115            {
6116                push_unproven_hit(terminal, ctx);
6117            }
6118            BareCallTargetResolution::FreeFunctions(_)
6119            | BareCallTargetResolution::UnprovenFreeFunctions(_)
6120            | BareCallTargetResolution::Type(_)
6121            | BareCallTargetResolution::CallableShadow => {}
6122            BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6123                push_unproven_hit(terminal, ctx);
6124            }
6125        }
6126        return;
6127    }
6128    if !free_function_call_may_target(node, text, ctx) {
6129        return;
6130    }
6131    if ctx.visibility.contains_named_symbol(
6132        ctx.file,
6133        text,
6134        TargetKind::FreeFunction,
6135        &ctx.spec.target,
6136    ) {
6137        push_hit(function_terminal_node(function), ctx);
6138    } else if ctx.visibility.resolve_known_non_target(
6139        ctx.file,
6140        text,
6141        TargetKind::FreeFunction,
6142        &ctx.spec.target,
6143    ) {
6144        // An explicitly namespace-qualified call to a different namespace (e.g. `other::run()` when
6145        // the target is `ns::run`) is a proven non-match, not an unresolved reference.
6146    } else {
6147        push_unproven_hit(function_terminal_node(function), ctx);
6148    }
6149}
6150
6151/// Recover a bare call whose adjacent object-like macro arguments made
6152/// tree-sitter place a `function_declarator` directly under an `ERROR` node.
6153///
6154/// For example, `check(mount(A, PREFIX SUFFIX, 0))` loses the inner
6155/// `call_expression`, but retains `mount(...)` as a structured declarator. A
6156/// real block-scope function declaration remains under a `declaration`, so the
6157/// direct `ERROR` parent and compound-statement ancestry keep this recovery
6158/// out of declaration syntax. The malformed parameter list cannot establish
6159/// arity; publish a proven hit only when ordinary visibility leaves one
6160/// callable identity for the bare name.
6161fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6162    if node.parent().is_none_or(|parent| parent.kind() != "ERROR")
6163        || !has_ancestor_kind(node, "compound_statement")
6164    {
6165        return;
6166    }
6167    let mut recovered_functions = Vec::new();
6168    if let Some(function) = node
6169        .child_by_field_name("declarator")
6170        .filter(|function| function.kind() == "identifier")
6171    {
6172        recovered_functions.push(function);
6173    }
6174    if let Some(parameters) = node.child_by_field_name("parameters") {
6175        let mut cursor = parameters.walk();
6176        for parameter in parameters
6177            .named_children(&mut cursor)
6178            .filter(|child| child.kind() == "parameter_declaration")
6179        {
6180            if parameter
6181                .child_by_field_name("declarator")
6182                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6183                && let Some(function) = parameter
6184                    .child_by_field_name("type")
6185                    .filter(|function| function.kind() == "type_identifier")
6186            {
6187                recovered_functions.push(function);
6188            }
6189        }
6190    }
6191
6192    for function in recovered_functions {
6193        let name = node_text(function, ctx.source);
6194        if !name_matches_callable(name, &ctx.spec.member_name)
6195            || ctx.local_shadows.is_shadowed(name)
6196        {
6197            continue;
6198        }
6199        *ctx.raw_match_count += 1;
6200        if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6201            push_hit(function, ctx);
6202            continue;
6203        }
6204        let mut candidates =
6205            ctx.visibility
6206                .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6207        candidates.retain(|candidate| {
6208            ctx.visibility.declaration_visible_at(
6209                &ctx.analyzer,
6210                ctx.file,
6211                candidate,
6212                function.start_byte(),
6213            )
6214        });
6215        dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6216        if candidates
6217            .iter()
6218            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6219        {
6220            push_unproven_hit(function, ctx);
6221        }
6222    }
6223}
6224
6225/// Whether the bare name at `call` binds to exactly one visible callable, and
6226/// that callable is the scan target.
6227///
6228/// This is the scan-side reading of the #1811 rule: with one name binding there
6229/// is nothing an unknown argument count could select instead, so the site is a
6230/// proven reference rather than an unproven one. Only bare identifiers qualify;
6231/// a member or qualified call reaches its target through a receiver this cannot
6232/// judge.
6233fn bare_name_binds_only_target(
6234    call: Node<'_>,
6235    function: Node<'_>,
6236    text: &str,
6237    ctx: &ScanCtx<'_>,
6238) -> bool {
6239    if !matches!(function.kind(), "identifier" | "template_function") {
6240        return false;
6241    }
6242    bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6243}
6244
6245fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6246    let mut candidates = ctx
6247        .visibility
6248        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6249    candidates.retain(|candidate| {
6250        ctx.visibility
6251            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6252    });
6253    dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6254    matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6255}
6256
6257fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6258    if ctx.spec.param_types.is_none() {
6259        return true;
6260    }
6261    let mut candidates = ctx
6262        .visibility
6263        .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6264    candidates.retain(|candidate| {
6265        ctx.visibility
6266            .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6267    });
6268    let Some(arity) = ctx
6269        .visibility
6270        .call_arity_evidence(ctx.file, call, ctx.source)
6271        .exact()
6272    else {
6273        return true;
6274    };
6275    candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
6276    if candidates.is_empty()
6277        || !candidates
6278            .iter()
6279            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6280    {
6281        return true;
6282    }
6283    let arg_types = call_argument_types(call, ctx);
6284    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6285        candidates,
6286        &arg_types,
6287        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6288        &|name| ctx.visibility.resolve_type(ctx.file, name),
6289        &|left, right| same_visible_symbol(left, right),
6290    );
6291    filtered
6292        .iter()
6293        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6294}
6295
6296/// The types of the arguments an `argument_list` or `initializer_list` supplies,
6297/// in order.
6298///
6299/// A `None` entry is an argument the index cannot type; one of them makes
6300/// `cpp_filter_candidates_by_args` keep every candidate rather than guess.
6301fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6302    argument_children(arguments)
6303        .map(|arg| expression_arg_type(arg, ctx))
6304        .collect()
6305}
6306
6307fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6308    call_arguments_node(call)
6309        .map(|arguments| argument_list_types(arguments, ctx))
6310        .unwrap_or_default()
6311}
6312
6313fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6314    match node.kind() {
6315        "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6316        | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6317            literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6318            literal
6319        }),
6320        "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6321        // `T v(x);` is the vexing parse: the grammar reads it as a function
6322        // declaration whose one parameter is `x` spelled as a type. A parameter
6323        // declaration that is nothing but a type identifier is that reading, and
6324        // the identifier it holds is the constructor argument.
6325        "parameter_declaration" => {
6326            let declared = node.child_by_field_name("type")?;
6327            (node.child_by_field_name("declarator").is_none()
6328                && declared.kind() == "type_identifier")
6329                .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6330                .flatten()
6331        }
6332        // `this->m_params` names the same data member as the bare `m_params`.
6333        "field_expression" => {
6334            let receiver = node
6335                .child_by_field_name("argument")
6336                .or_else(|| node.named_child(0))?;
6337            let field = node.child_by_field_name("field")?;
6338            (receiver.kind() == "this")
6339                .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6340                .flatten()
6341        }
6342        "parenthesized_expression" => node
6343            .child_by_field_name("argument")
6344            .or_else(|| node.named_child(0))
6345            .and_then(|inner| expression_arg_type(inner, ctx)),
6346        // `std::move(t)` and `std::forward<T>(t)` have the type of what they
6347        // forward (#2552).
6348        "call_expression" => {
6349            let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6350            expression_arg_type(forwarded, ctx)
6351        }
6352        "pointer_expression" => {
6353            let delta = match node.child_by_field_name("operator")?.kind() {
6354                "&" => 1,
6355                "*" => -1,
6356                _ => return None,
6357            };
6358            let inner = node
6359                .child_by_field_name("argument")
6360                .or_else(|| node.named_child(0))?;
6361            let mut arg_type = expression_arg_type(inner, ctx)?;
6362            arg_type.indirection += delta;
6363            Some(arg_type)
6364        }
6365        _ => None,
6366    }
6367}
6368
6369/// The type of a bare value name used as a call argument: the local or
6370/// parameter binding when one is in scope, otherwise the enclosing class's data
6371/// member of that name.
6372///
6373/// Unqualified lookup reaches a local before a data member, so a local of the
6374/// same name ends the search rather than falling through to the field. This
6375/// mirrors `cpp_identifier_value_type` on the forward side, which is what keeps
6376/// the two directions agreeing about which overload a member-field argument
6377/// selects (#2894).
6378fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6379    match ctx.bindings.resolve_symbol(name) {
6380        SymbolResolution::Precise(bindings) => {
6381            bindings.iter().find_map(CppScanBinding::as_arg_type)
6382        }
6383        SymbolResolution::Ambiguous => None,
6384        SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
6385            .then(|| enclosing_member_field_arg_type(node, name, ctx))
6386            .flatten(),
6387    }
6388}
6389
6390/// The declared type of `name` read as a data member of the class that lexically
6391/// owns `node`.
6392///
6393/// `None` when there is no enclosing class, when the name is not one data member
6394/// of it, or when the member's declared type does not resolve. An unknown
6395/// argument type makes the overload filter keep every candidate, which is the
6396/// honest answer for a field the index cannot type.
6397fn enclosing_member_field_arg_type(
6398    node: Node<'_>,
6399    name: &str,
6400    ctx: &ScanCtx<'_>,
6401) -> Option<CppArgType> {
6402    let owner = enclosing_context(node, ctx).owner?;
6403    let fields = ctx
6404        .visibility
6405        .visible_members_for_owner_name(ctx.file, &owner, name)
6406        .into_iter()
6407        .filter(|unit| unit.is_field())
6408        .collect::<Vec<_>>();
6409    let [field] = fields.as_slice() else {
6410        return None;
6411    };
6412    let (type_name, unit, indirection) =
6413        field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
6414    Some(CppArgType {
6415        name: type_name,
6416        unit,
6417        indirection,
6418        pointee_const: false,
6419    })
6420}
6421
6422/// Record a *non-call* reference to a free function used as a value: `&foo`,
6423/// `fp = foo`, `foo` passed as an argument, etc. The callee identifier of a call
6424/// `foo()` is recorded by the call_expression arm, and the function's own
6425/// declaration/definition name is not a reference.
6426fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6427    let text = node_text(node, ctx.source);
6428    if !name_matches_callable(text, &ctx.spec.member_name) {
6429        return;
6430    }
6431    if is_declaration_name(node) {
6432        maybe_record_free_function_declaration_reference(node, ctx);
6433        return;
6434    }
6435    if is_call_callee_node(node) {
6436        return;
6437    }
6438    *ctx.raw_match_count += 1;
6439    if ctx.visibility.contains_named_symbol(
6440        ctx.file,
6441        text,
6442        TargetKind::FreeFunction,
6443        &ctx.spec.target,
6444    ) {
6445        push_hit(node, ctx);
6446    } else if ctx.visibility.resolve_known_non_target(
6447        ctx.file,
6448        text,
6449        TargetKind::FreeFunction,
6450        &ctx.spec.target,
6451    ) {
6452        // A qualified reference proven to a different namespace is not a match.
6453    } else {
6454        push_unproven_hit(node, ctx);
6455    }
6456}
6457
6458fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6459    if !ctx.spec.callable_has_definition_body {
6460        return;
6461    }
6462    let text = node_text(node, ctx.source);
6463    if !name_matches_callable(text, &ctx.spec.member_name) {
6464        return;
6465    }
6466    let mut declaration = node.parent();
6467    while let Some(candidate) = declaration {
6468        if candidate.kind() == "function_definition" {
6469            return;
6470        }
6471        if candidate.kind() == "declaration" {
6472            break;
6473        }
6474        declaration = candidate.parent();
6475    }
6476    let Some(declaration) = declaration else {
6477        return;
6478    };
6479    let signature = node_text(declaration, ctx.source);
6480    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6481        && !expected.accepts(signature_arity(Some(signature)))
6482    {
6483        return;
6484    }
6485    let linked_declaration = ctx
6486        .visibility
6487        .named_candidates(ctx.file, text, TargetKind::FreeFunction)
6488        .into_iter()
6489        .find(|candidate| {
6490            candidate.source() == ctx.file
6491                && candidate.is_function()
6492                && (!ctx.target_group.contains(candidate)
6493                    || candidate.source() == ctx.spec.target.source())
6494                && ctx.analyzer.ranges(candidate).iter().any(|range| {
6495                    range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6496                })
6497                && (candidate.source() == ctx.spec.target.source()
6498                    && candidate.fq_name() == ctx.spec.target.fq_name()
6499                    && candidate.signature() == ctx.spec.target.signature()
6500                    || cpp_callable_definitions_share_identity_evidence_with_visibility(
6501                        &ctx.analyzer,
6502                        ctx.visibility,
6503                        candidate,
6504                        &ctx.spec.target,
6505                    ))
6506        });
6507    if linked_declaration.is_none() {
6508        return;
6509    }
6510    *ctx.raw_match_count += 1;
6511    push_declaration_reference_hit(node, ctx);
6512}
6513
6514fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6515    let Some(function) = function_definition_name_node(node) else {
6516        return;
6517    };
6518    let text = node_text(function, ctx.source);
6519    if !name_matches_callable(text, &ctx.spec.member_name) {
6520        return;
6521    }
6522    *ctx.raw_match_count += 1;
6523    if !function_definition_signature_matches_target(node, ctx) {
6524        return;
6525    }
6526    if definition_name_candidates(function, ctx)
6527        .iter()
6528        .any(|name| {
6529            ctx.visibility.contains_named_symbol(
6530                ctx.file,
6531                name,
6532                TargetKind::FreeFunction,
6533                &ctx.spec.target,
6534            )
6535        })
6536    {
6537        push_definition_hit(function, ctx);
6538    } else if definition_name_candidates(function, ctx)
6539        .iter()
6540        .any(|name| {
6541            ctx.visibility.resolve_known_non_target(
6542                ctx.file,
6543                name,
6544                TargetKind::FreeFunction,
6545                &ctx.spec.target,
6546            )
6547        })
6548    {
6549        // A definition in another explicit namespace is a proven non-match.
6550    } else {
6551        push_unproven_definition_hit(function, ctx);
6552    }
6553}
6554
6555fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6556    if node.kind() == "preproc_arg" {
6557        maybe_record_function_macro_replacement_method_hits(node, ctx);
6558        return;
6559    }
6560    if node.kind() == "using_declaration" {
6561        maybe_record_using_callable_hit(node, ctx);
6562        return;
6563    }
6564    if has_ancestor_kind(node, "using_declaration") {
6565        return;
6566    }
6567    if node.kind() == "function_definition" {
6568        maybe_record_method_definition_hit(node, ctx);
6569        return;
6570    }
6571    if is_declaration_name(node) {
6572        return;
6573    }
6574    if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
6575        maybe_record_qualified_method_value_hit(node, member, ctx);
6576        return;
6577    }
6578    if let Some(value) = qualified_callable_value(node) {
6579        maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
6580        return;
6581    }
6582    if let Some(call) = recovered_relational_template_member_call(node) {
6583        maybe_record_recovered_relational_template_method_hit(call, ctx);
6584        return;
6585    }
6586    if node.kind() != "call_expression" {
6587        return;
6588    }
6589    if let Some((receiver, operator)) = explicit_operator_call(node) {
6590        let text = node_text(operator, ctx.source);
6591        if !name_matches_callable(text, &ctx.spec.member_name) {
6592            return;
6593        }
6594        *ctx.raw_match_count += 1;
6595        if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6596            match ctx
6597                .visibility
6598                .call_arity_evidence(ctx.file, node, ctx.source)
6599                .accepts(expected)
6600            {
6601                Some(true) => {}
6602                Some(false) => return,
6603                None => {
6604                    push_unproven_hit(operator, ctx);
6605                    return;
6606                }
6607            }
6608        }
6609        match explicit_receiver_target_resolution(
6610            receiver,
6611            ctx.visibility
6612                .call_arity_evidence(ctx.file, node, ctx.source)
6613                .exact(),
6614            ctx,
6615        ) {
6616            MethodReceiverTargetResolution::Target
6617                if receiver_is_self_like(
6618                    receiver,
6619                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
6620                ) =>
6621            {
6622                push_self_receiver_hit(operator, ctx);
6623            }
6624            MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
6625            MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
6626            MethodReceiverTargetResolution::NonTarget
6627            | MethodReceiverTargetResolution::Ambiguous => {}
6628        }
6629        return;
6630    }
6631    let Some(function) = node
6632        .child_by_field_name("function")
6633        .or_else(|| node.named_child(0))
6634    else {
6635        return;
6636    };
6637    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6638        return;
6639    }
6640    if function.kind() == "identifier"
6641        && ctx
6642            .local_shadows
6643            .is_shadowed(node_text(function, ctx.source))
6644    {
6645        return;
6646    }
6647    *ctx.raw_match_count += 1;
6648    if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6649        match ctx
6650            .visibility
6651            .call_arity_evidence(ctx.file, node, ctx.source)
6652            .accepts(expected)
6653        {
6654            Some(true) => {}
6655            Some(false) => return,
6656            None => {
6657                push_unproven_hit(function_terminal_node(function), ctx);
6658                return;
6659            }
6660        }
6661    }
6662    if !method_call_may_target(node, ctx) {
6663        return;
6664    }
6665    if is_structurally_qualified(function) {
6666        match qualified_owner_resolution(function, ctx) {
6667            QualifiedOwnerResolution::Target => {
6668                push_hit(function_terminal_node(function), ctx);
6669            }
6670            QualifiedOwnerResolution::NonTarget => {}
6671            QualifiedOwnerResolution::Unresolved => {
6672                push_unproven_hit(function_terminal_node(function), ctx);
6673            }
6674        }
6675        return;
6676    }
6677    match call_function_target_resolution(function, ctx) {
6678        MethodReceiverTargetResolution::Target
6679            if call_function_has_direct_self_receiver(
6680                function,
6681                ctx.analyzer.reference_uses_c_semantics(ctx.file),
6682            ) =>
6683        {
6684            push_self_receiver_hit(function_terminal_node(function), ctx);
6685        }
6686        MethodReceiverTargetResolution::Target => {
6687            push_hit(function_terminal_node(function), ctx);
6688        }
6689        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
6690        // A bare `m()` whose name resolves through the enclosing class's base hierarchy to
6691        // the target member declared on a base is a genuine external usage of that inherited
6692        // base member (e.g. `Derived::run` calling inherited `Base::value`), so it is an
6693        // ordinary Reference hit -- not a same-type self call. Checked before the self-owner
6694        // arm because `same_owner_context` also accepts this inherited case.
6695        MethodReceiverTargetResolution::Missing
6696            if inherited_target_owner_context(function, ctx) =>
6697        {
6698            push_hit(function_terminal_node(function), ctx);
6699        }
6700        MethodReceiverTargetResolution::Missing
6701            if (matches!(function.kind(), "identifier" | "template_function")
6702                || call_function_has_direct_self_receiver(
6703                    function,
6704                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
6705                ))
6706                && (same_owner_context(function, ctx)
6707                    || out_of_line_target_owner_context(function, ctx)) =>
6708        {
6709            push_self_receiver_hit(function_terminal_node(function), ctx);
6710        }
6711        MethodReceiverTargetResolution::Missing
6712            if function.kind() == "identifier"
6713                && resolves_to_lexical_free_function(function, ctx) =>
6714        {
6715            // A visible namespace/free function is a proven negative once the
6716            // enclosing structured owner and its hierarchy contain no such member.
6717        }
6718        MethodReceiverTargetResolution::Missing
6719            if !receiver_has_known_non_target(function, ctx)
6720                && !known_non_target_owner_context(function, ctx) =>
6721        {
6722            push_unproven_hit(function_terminal_node(function), ctx);
6723        }
6724        MethodReceiverTargetResolution::Missing => {}
6725    }
6726}
6727
6728/// Report member calls written inside a function-like macro's replacement.
6729///
6730/// Tree-sitter keeps the whole replacement of `#define NAME(a) ...` as one
6731/// opaque `preproc_arg`, so the ordinary member-call path never sees the calls
6732/// it contains (#2549). The resolver's shared sentinel parse recovers them as
6733/// structure, and every hit is reported at the bytes the member spells inside
6734/// that token.
6735fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6736    let Some(definition) = node.parent().filter(|parent| {
6737        parent.kind() == "preproc_function_def"
6738            && parent
6739                .child_by_field_name("value")
6740                .is_some_and(|value| same_node(value, node))
6741    }) else {
6742        return;
6743    };
6744    let Some(body) = ctx
6745        .visibility
6746        .function_macro_replacement_body(ctx.file, definition, ctx.source)
6747    else {
6748        return;
6749    };
6750    let Some(statements) = body.statements() else {
6751        return;
6752    };
6753    // Collect the candidate calls before typing anything: a replacement that
6754    // never names the queried member must not pay for its declarations.
6755    let mut candidates = Vec::new();
6756    let mut stack = vec![statements];
6757    while let Some(current) = stack.pop() {
6758        for index in (0..current.named_child_count()).rev() {
6759            if let Some(child) = current.named_child(index) {
6760                stack.push(child);
6761            }
6762        }
6763        if current.kind() != "call_expression" {
6764            continue;
6765        }
6766        let Some(function) = current
6767            .child_by_field_name("function")
6768            .filter(|function| function.kind() == "field_expression")
6769        else {
6770            continue;
6771        };
6772        let Some(member) = function.child_by_field_name("field") else {
6773            continue;
6774        };
6775        if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
6776            continue;
6777        }
6778        let Some(receiver) = function
6779            .child_by_field_name("argument")
6780            .or_else(|| function.child_by_field_name("object"))
6781            .or_else(|| function.named_child(0))
6782        else {
6783            continue;
6784        };
6785        candidates.push((current, member, receiver));
6786    }
6787    if candidates.is_empty() {
6788        return;
6789    }
6790    let locals = macro_replacement_local_receivers(statements, &body, ctx);
6791    for (current, member, receiver) in candidates {
6792        if *ctx.limit_exceeded {
6793            return;
6794        }
6795        let range = body.file_range(member, node.start_byte());
6796        debug_assert_eq!(
6797            ctx.source.get(range.clone()),
6798            Some(node_text(member, &body.source)),
6799            "macro replacement member range must spell the member name"
6800        );
6801        *ctx.raw_match_count += 1;
6802        // The replacement's own macro environment is the one at the definition,
6803        // not at the sentinel offsets the recovered call carries.
6804        let arity_evidence = ctx.visibility.call_arity_evidence_at(
6805            ctx.file,
6806            current,
6807            &body.source,
6808            definition.start_byte(),
6809        );
6810        if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
6811            match arity_evidence.accepts(expected) {
6812                Some(true) => {}
6813                Some(false) => continue,
6814                None => {
6815                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
6816                    continue;
6817                }
6818            }
6819        }
6820        let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
6821        {
6822            MacroReplacementReceiver::Parameter => {
6823                if target_member_is_visible_candidate(ctx) {
6824                    push_unproven_reference_hit_range(node, range.start, range.end, ctx);
6825                }
6826                continue;
6827            }
6828            MacroReplacementReceiver::Unknown => continue,
6829            MacroReplacementReceiver::Units(units) => {
6830                declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
6831            }
6832        };
6833        match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
6834            MethodReceiverTargetResolution::Target => {
6835                push_reference_hit_range(node, range.start, range.end, ctx);
6836            }
6837            MethodReceiverTargetResolution::Missing => {
6838                push_unproven_reference_hit_range(node, range.start, range.end, ctx);
6839            }
6840            MethodReceiverTargetResolution::NonTarget
6841            | MethodReceiverTargetResolution::Ambiguous => {}
6842        }
6843    }
6844}
6845
6846enum MacroReplacementReceiver {
6847    /// The receiver is one of the macro's parameters. Its type is supplied by
6848    /// each invocation, so a member call on it can never be proven here.
6849    Parameter,
6850    /// The receiver's declared type resolved to these units.
6851    Units(Vec<CodeUnit>),
6852    /// The receiver has a shape this path does not type.
6853    Unknown,
6854}
6855
6856fn macro_replacement_receiver(
6857    receiver: Node<'_>,
6858    body: &ParsedReplacementBody,
6859    locals: &HashMap<String, Option<CodeUnit>>,
6860    anchor: Node<'_>,
6861    ctx: &ScanCtx<'_>,
6862) -> MacroReplacementReceiver {
6863    let mut current = receiver;
6864    while matches!(
6865        current.kind(),
6866        "parenthesized_expression" | "pointer_expression"
6867    ) {
6868        let Some(inner) = current
6869            .child_by_field_name("argument")
6870            .or_else(|| current.named_child(0))
6871        else {
6872            return MacroReplacementReceiver::Unknown;
6873        };
6874        current = inner;
6875    }
6876    if !matches!(current.kind(), "identifier" | "field_identifier") {
6877        return MacroReplacementReceiver::Unknown;
6878    }
6879    let name = node_text(current, &body.source);
6880    if body.parameters.iter().any(|parameter| parameter == name) {
6881        return MacroReplacementReceiver::Parameter;
6882    }
6883    if let Some(local) = locals.get(name) {
6884        return match local {
6885            Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
6886            None => MacroReplacementReceiver::Unknown,
6887        };
6888    }
6889    // Neither a parameter nor a replacement-local: an ordinary name the macro
6890    // definition site sees, resolved exactly as the ordinary receiver path
6891    // resolves a bare identifier.
6892    let global_fields = ctx
6893        .visibility
6894        .visible_identifier_candidates(ctx.file, name)
6895        .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
6896        .collect::<Vec<_>>();
6897    if !global_fields.is_empty() {
6898        return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
6899            global_fields,
6900            anchor,
6901            ctx,
6902        ));
6903    }
6904    MacroReplacementReceiver::Units(
6905        ctx.visibility
6906            .resolve_type(ctx.file, name)
6907            .into_iter()
6908            .collect(),
6909    )
6910}
6911
6912/// Type the locals a macro replacement declares, keyed by declared name.
6913///
6914/// `Catch::AssertionHandler h(expr); h.handleExpr(expr);` is the shape this
6915/// serves: the receiver's type is stated inside the replacement itself. A name
6916/// whose declared type does not resolve is kept with no unit, so a call on it
6917/// stays silent instead of falling through to file-scope name lookup.
6918fn macro_replacement_local_receivers(
6919    statements: Node<'_>,
6920    body: &ParsedReplacementBody,
6921    ctx: &ScanCtx<'_>,
6922) -> HashMap<String, Option<CodeUnit>> {
6923    let mut locals = HashMap::default();
6924    let mut stack = vec![statements];
6925    while let Some(current) = stack.pop() {
6926        for index in (0..current.named_child_count()).rev() {
6927            if let Some(child) = current.named_child(index) {
6928                stack.push(child);
6929            }
6930        }
6931        if current.kind() != "declaration" {
6932            continue;
6933        }
6934        let Some(type_node) = current
6935            .child_by_field_name("type")
6936            .or_else(|| first_type_child(current))
6937        else {
6938            continue;
6939        };
6940        let unit = macro_replacement_declared_unit(type_node, body, ctx);
6941        let mut cursor = current.walk();
6942        for child in current.named_children(&mut cursor) {
6943            let declarator = if child.kind() == "init_declarator" {
6944                child.child_by_field_name("declarator")
6945            } else {
6946                is_declarator_node(child).then_some(child)
6947            };
6948            let Some(name) =
6949                declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
6950            else {
6951                continue;
6952            };
6953            locals.insert(name, unit.clone());
6954        }
6955    }
6956    locals
6957}
6958
6959fn macro_replacement_declared_unit(
6960    type_node: Node<'_>,
6961    body: &ParsedReplacementBody,
6962    ctx: &ScanCtx<'_>,
6963) -> Option<CodeUnit> {
6964    let name = normalize_cpp_type_name(node_text(type_node, &body.source));
6965    let unit = match ctx
6966        .visibility
6967        .resolve_type_node_result(ctx.file, type_node, &body.source)
6968    {
6969        Ok(Some(unit)) => Some(unit),
6970        Ok(None) => ctx
6971            .visibility
6972            .canonical_type_for_reference(ctx.file, &name)
6973            .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
6974        Err(_) => None,
6975    }?;
6976    canonical_receiver_unit(&unit, ctx)
6977}
6978
6979/// Whether the queried member is a candidate this file can see.
6980///
6981/// A macro parameter receiver has no type at the definition, so a member call
6982/// on it is at most unproven. Requiring the target to be visible here keeps a
6983/// same-named member of an unrelated translation unit out of the result.
6984fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
6985    ctx.visibility
6986        .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6987        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6988}
6989
6990fn maybe_record_recovered_relational_template_method_hit(
6991    call: RecoveredRelationalTemplateMemberCall<'_>,
6992    ctx: &mut ScanCtx<'_>,
6993) {
6994    if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
6995        return;
6996    }
6997    *ctx.raw_match_count += 1;
6998    if !ctx
6999        .visibility
7000        .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7001        || ctx
7002            .spec
7003            .callable_arity_at(call.member.start_byte())
7004            .is_some_and(|arity| !arity.accepts(call.arity))
7005    {
7006        return;
7007    }
7008    match explicit_receiver_target_resolution(call.receiver, Some(call.arity), ctx) {
7009        MethodReceiverTargetResolution::Target
7010            if receiver_is_self_like(
7011                call.receiver,
7012                ctx.analyzer.reference_uses_c_semantics(ctx.file),
7013            ) =>
7014        {
7015            push_self_receiver_hit(call.member, ctx);
7016        }
7017        MethodReceiverTargetResolution::Target => push_hit(call.member, ctx),
7018        MethodReceiverTargetResolution::Missing => push_unproven_hit(call.member, ctx),
7019        MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7020    }
7021}
7022
7023fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7024    if node.kind() != "qualified_identifier" {
7025        return None;
7026    }
7027    let parameter = node
7028        .parent()
7029        .filter(|parent| parent.kind() == "parameter_declaration")?;
7030    let parameter_declarator = parameter.child_by_field_name("declarator")?;
7031    // Tree-sitter recovers `Value value(Owner::method(arg));` as a function
7032    // declaration whose sole pseudo-parameter has `Owner::method` as its type
7033    // and `(arg)` as an abstract function declarator. Ordinary qualified
7034    // parameter types have named/pointer/reference declarators instead.
7035    if parameter.child_by_field_name("type") != Some(node)
7036        || parameter_declarator.kind() != "abstract_function_declarator"
7037    {
7038        return None;
7039    }
7040    let parameter_list = parameter
7041        .parent()
7042        .filter(|parent| parent.kind() == "parameter_list")?;
7043    if parameter_list.named_child_count() != 1 {
7044        return None;
7045    }
7046    let function_declarator = parameter_list
7047        .parent()
7048        .filter(|parent| parent.kind() == "function_declarator")?;
7049    if function_declarator
7050        .child_by_field_name("declarator")
7051        .is_none_or(|declarator| declarator.kind() != "identifier")
7052        || function_declarator
7053            .parent()
7054            .is_none_or(|parent| parent.kind() != "declaration")
7055    {
7056        return None;
7057    }
7058    node.child_by_field_name("name")
7059}
7060
7061fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7062    let Some(imported) = ordinary_using_declaration_type_node(node) else {
7063        return;
7064    };
7065    if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7066        return;
7067    }
7068    let Some(target_owner) = ctx.spec.owner.as_ref() else {
7069        return;
7070    };
7071    *ctx.raw_match_count += 1;
7072    let owner_resolution = qualified_owner_components(imported, ctx.source)
7073        .map(|qualified| {
7074            let lexical_scope = match enclosing_lexical_scope_components(
7075                imported,
7076                &ctx.analyzer,
7077                ctx.visibility,
7078                ctx.file,
7079                ctx.source,
7080            ) {
7081                LexicalScopeResolution::Resolved(scope) => scope,
7082                LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7083                LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7084            };
7085            ctx.visibility.resolve_type_components_lexically(
7086                &ctx.analyzer,
7087                ctx.file,
7088                &qualified.names,
7089                qualified.global,
7090                &lexical_scope,
7091            )
7092        })
7093        .unwrap_or(LexicalTypeResolution::Missing);
7094    let matches_target_owner = matches!(
7095        owner_resolution,
7096        LexicalTypeResolution::Resolved {
7097            ref unit,
7098            ref candidates,
7099            ..
7100        } if same_visible_symbol(unit, target_owner)
7101            || candidates
7102                .iter()
7103                .any(|candidate| same_visible_symbol(candidate, target_owner))
7104    );
7105    if !matches_target_owner {
7106        match owner_resolution {
7107            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7108                push_unproven_hit(imported, ctx);
7109            }
7110            LexicalTypeResolution::Resolved { .. } => {}
7111        }
7112        return;
7113    }
7114    match ctx.visibility.visible_member_for_owner_name(
7115        ctx.file,
7116        target_owner,
7117        &ctx.spec.member_name,
7118    ) {
7119        VisibleMemberResolution::Callable(candidates)
7120            if candidates.iter().all(|candidate| {
7121                ctx.target_group.contains(candidate)
7122                    || ctx
7123                        .target_group
7124                        .iter()
7125                        .any(|target| same_visible_symbol(candidate, target))
7126            }) =>
7127        {
7128            push_hit(imported, ctx);
7129        }
7130        VisibleMemberResolution::NonCallable => {}
7131        VisibleMemberResolution::Callable(_)
7132        | VisibleMemberResolution::AmbiguousKind
7133        | VisibleMemberResolution::Missing => {
7134            push_unproven_hit(imported, ctx);
7135        }
7136    }
7137}
7138
7139fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7140    let name = node_text(node, ctx.source);
7141    let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7142    let key = (namespace.clone(), name.to_string());
7143    if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7144        return resolved;
7145    }
7146    let resolved = ctx
7147        .visibility
7148        .visible_identifier_candidates(ctx.file, name)
7149        .any(|unit| {
7150            unit.is_function()
7151                && type_owner_of(&ctx.analyzer, unit).is_none()
7152                && unit.package_name() == namespace
7153        });
7154    ctx.lexical_free_function_cache
7155        .borrow_mut()
7156        .insert(key, resolved);
7157    resolved
7158}
7159
7160fn maybe_record_qualified_method_value_hit(
7161    qualified: Node<'_>,
7162    member: Node<'_>,
7163    ctx: &mut ScanCtx<'_>,
7164) {
7165    if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7166        return;
7167    }
7168    *ctx.raw_match_count += 1;
7169    let resolution =
7170        qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7171    match resolution {
7172        LexicalCallableValueResolution::Type(resolved_owner) => {
7173            let Some(owner) = ctx.spec.owner.as_ref() else {
7174                push_unproven_hit(member, ctx);
7175                return;
7176            };
7177            if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7178                if same_visible_symbol(&resolved_owner, owner) {
7179                    push_unproven_hit(member, ctx);
7180                }
7181                return;
7182            }
7183            match ctx.visibility.visible_member_for_owner_name(
7184                ctx.file,
7185                owner,
7186                &ctx.spec.member_name,
7187            ) {
7188                VisibleMemberResolution::Callable(candidates)
7189                    if candidates.iter().all(|candidate| {
7190                        ctx.target_group.contains(candidate)
7191                            || ctx
7192                                .target_group
7193                                .iter()
7194                                .any(|target| same_visible_symbol(candidate, target))
7195                    }) =>
7196                {
7197                    // An explicitly qualified method value remains an external
7198                    // reference even when its owner is the enclosing class.
7199                    push_hit(member, ctx);
7200                }
7201                VisibleMemberResolution::NonCallable => {}
7202                VisibleMemberResolution::Callable(_)
7203                | VisibleMemberResolution::AmbiguousKind
7204                | VisibleMemberResolution::Missing => {
7205                    push_unproven_hit(member, ctx);
7206                }
7207            }
7208        }
7209        LexicalCallableValueResolution::FreeFunction(_) => {}
7210        LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7211            push_unproven_hit(member, ctx);
7212        }
7213    }
7214}
7215
7216fn qualified_callable_value_resolution(
7217    qualified: Node<'_>,
7218    member_name: &str,
7219    ctx: &ScanCtx<'_>,
7220) -> LexicalCallableValueResolution {
7221    let Some((owner_components, global)) =
7222        qualified_callable_owner_components(qualified, ctx.source)
7223    else {
7224        return LexicalCallableValueResolution::Missing;
7225    };
7226    let lexical_scope = if global {
7227        Vec::new()
7228    } else {
7229        match enclosing_lexical_scope_components(
7230            qualified,
7231            &ctx.analyzer,
7232            ctx.visibility,
7233            ctx.file,
7234            ctx.source,
7235        ) {
7236            LexicalScopeResolution::Resolved(scope) => scope,
7237            LexicalScopeResolution::Ambiguous => {
7238                return LexicalCallableValueResolution::Ambiguous;
7239            }
7240            LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7241        }
7242    };
7243    if let Some(target_owner) = ctx.spec.owner.as_ref()
7244        && let LexicalTypeResolution::Resolved { unit, .. } =
7245            resolve_type_components_lexically_at_for_target_with_scope_cache(
7246                qualified,
7247                &owner_components,
7248                global,
7249                &ctx.analyzer,
7250                ctx.visibility,
7251                &ctx.ordinary_type_imports,
7252                ctx.file,
7253                ctx.source,
7254                target_owner,
7255                false,
7256                Some(&ctx.lexical_scope_cache),
7257            )
7258    {
7259        return LexicalCallableValueResolution::Type(unit);
7260    }
7261    ctx.visibility.resolve_callable_value_components_lexically(
7262        &ctx.analyzer,
7263        ctx.file,
7264        &owner_components,
7265        member_name,
7266        global,
7267        &lexical_scope,
7268    )
7269}
7270
7271fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7272    let Some(owner) = ctx.spec.owner.as_ref() else {
7273        return true;
7274    };
7275    if ctx.spec.param_types.is_none() {
7276        return true;
7277    }
7278    let mut candidates = ctx
7279        .visibility
7280        .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7281        .into_iter()
7282        .filter(|unit| unit.is_function())
7283        .cloned()
7284        .collect::<Vec<_>>();
7285    let Some(arity) = ctx
7286        .visibility
7287        .call_arity_evidence(ctx.file, call, ctx.source)
7288        .exact()
7289    else {
7290        return true;
7291    };
7292    candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
7293    if candidates.is_empty()
7294        || !candidates
7295            .iter()
7296            .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7297    {
7298        return true;
7299    }
7300    let arg_types = call_argument_types(call, ctx);
7301    let filtered = cpp_filter_candidates_by_args_with_parameter_types(
7302        candidates,
7303        &arg_types,
7304        &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
7305        &|name| ctx.visibility.resolve_type(ctx.file, name),
7306        &|left, right| same_visible_symbol(left, right),
7307    );
7308    filtered
7309        .iter()
7310        .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7311}
7312
7313fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7314    let Some(function) = function_definition_name_node(node) else {
7315        return;
7316    };
7317    if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7318        return;
7319    }
7320    *ctx.raw_match_count += 1;
7321    if !function_definition_signature_matches_target(node, ctx) {
7322        return;
7323    }
7324    if node_inside_target_declaration(function, ctx) {
7325        return;
7326    }
7327    if is_structurally_qualified(function) {
7328        match qualified_owner_resolution(function, ctx) {
7329            QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
7330            QualifiedOwnerResolution::NonTarget => {}
7331            QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7332        }
7333        return;
7334    }
7335    if definition_name_candidates(function, ctx)
7336        .iter()
7337        .any(|name| {
7338            name.contains("::")
7339                && ctx.visibility.contains_named_symbol(
7340                    ctx.file,
7341                    name,
7342                    TargetKind::Method,
7343                    &ctx.spec.target,
7344                )
7345        })
7346    {
7347        push_definition_hit(function, ctx);
7348    } else if definition_name_candidates(function, ctx)
7349        .iter()
7350        .any(|name| {
7351            ctx.visibility.resolve_known_non_target(
7352                ctx.file,
7353                name,
7354                TargetKind::Method,
7355                &ctx.spec.target,
7356            )
7357        })
7358        || known_non_target_owner_context(function, ctx)
7359    {
7360        // A method definition for another visible owner is a proven non-match.
7361    } else {
7362        push_unproven_definition_hit(function, ctx);
7363    }
7364}
7365
7366fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7367    ctx.target_declaration_ranges
7368        .iter()
7369        .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
7370}
7371
7372fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
7373    let mut receiver = None;
7374    let mut cursor = node.walk();
7375    for child in node.named_children(&mut cursor) {
7376        if child.kind() == "argument_list" {
7377            continue;
7378        }
7379        if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
7380            return receiver.map(|receiver| (receiver, operator));
7381        }
7382        if receiver.is_none() {
7383            receiver = Some(child);
7384        }
7385    }
7386    None
7387}
7388
7389fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
7390    if node.kind() != "function_definition" {
7391        return None;
7392    }
7393    node.child_by_field_name("declarator")
7394        .and_then(declarator_name_node)
7395}
7396
7397fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
7398    function_definition_name_node(node)
7399}
7400
7401fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7402    let definition = node_text(node, ctx.source);
7403    let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7404        return true;
7405    };
7406    if !expected.accepts(signature_arity(Some(definition))) {
7407        return false;
7408    }
7409    let Some(target_signature) = ctx.spec.target.signature() else {
7410        return true;
7411    };
7412    cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
7413}
7414
7415fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
7416    name_matches_callable(node_text(function_terminal_node(node), source), expected)
7417}
7418
7419fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
7420    let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
7421    if raw.is_empty() {
7422        return Vec::new();
7423    }
7424    let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
7425        return vec![raw];
7426    };
7427    if !raw.contains("::") {
7428        return vec![format!("{namespace}::{raw}")];
7429    }
7430    // fqname-M4: peeks at the raw first `::`-split token, including the empty
7431    // token a leading-`::` absolute reference (`::Foo::Bar`) produces (same
7432    // shape as rust's `rust_reference_looks_external`); the shared structured
7433    // splitter filters empty segments, which would shift "which token is
7434    // first" for that one lead-`::` shape and is not proven equivalent here.
7435    if raw
7436        .split("::")
7437        .next()
7438        .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
7439    {
7440        vec![format!("{namespace}::{raw}"), raw]
7441    } else {
7442        vec![raw]
7443    }
7444}
7445
7446fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7447    if node.kind() == kind {
7448        return Some(node);
7449    }
7450    let mut cursor = node.walk();
7451    for child in node.named_children(&mut cursor) {
7452        if let Some(found) = first_descendant_of_kind(child, kind) {
7453            return Some(found);
7454        }
7455    }
7456    None
7457}
7458
7459/// Whether a name leaf the field scans reached sits in a value position.
7460///
7461/// Every kind those scans admit already names a value except `type_identifier`.
7462/// tree-sitter spells a non-type template argument with type syntax, so the
7463/// constant `N` in `std::array<W, N>` is a `type_identifier` exactly like the
7464/// type argument `W` beside it ([`is_type_shaped_template_argument_name`]).
7465/// Admit that leaf only where no visible type explains the spelling, which is
7466/// the order forward navigation applies before it reads the leaf in the value
7467/// namespace (#2556). Every other `type_identifier` is a type reference and
7468/// belongs to the type scan.
7469fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7470    node.kind() != "type_identifier"
7471        || (is_type_shaped_template_argument_name(node)
7472            && ctx
7473                .visibility
7474                .resolve_type(ctx.file, node_text(node, ctx.source))
7475                .is_none())
7476}
7477
7478fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7479    if matches!(node.kind(), "identifier" | "field_identifier")
7480        && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
7481    {
7482        return;
7483    }
7484    if !matches!(
7485        node.kind(),
7486        "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
7487    ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7488        || !scan_leaf_is_value_position(node, ctx)
7489        || is_declaration_name(node)
7490        || is_member_field_own_declarator(node, ctx)
7491        || is_selected_field_expression_member_descendant(node)
7492        || is_nested_in_qualified_identifier(node)
7493    {
7494        return;
7495    }
7496    *ctx.raw_match_count += 1;
7497    if global_field_resolves_to_target(node, ctx) {
7498        push_hit(node, ctx);
7499    } else if global_field_is_known_non_target(node, ctx) {
7500    } else {
7501        push_unproven_hit(node, ctx);
7502    }
7503}
7504
7505/// Whether `node` belongs to the selected-member side of any enclosing field
7506/// expression. A reference may be nested arbitrarily inside the receiver side
7507/// (for example, an argument to a call-built fluent receiver), so direct child
7508/// equality is insufficient: classify each ancestor by structured subtree
7509/// containment instead.
7510fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
7511    let candidate = node;
7512    while let Some(parent) = node.parent() {
7513        if parent.kind() == "field_expression" {
7514            if let Some(field) = parent.child_by_field_name("field")
7515                && node_is_within(field, candidate)
7516            {
7517                // The `template` disambiguator in `receiver.template f<Arg>()`
7518                // wraps the selected member in a `dependent_name` whose single
7519                // named child is the `template_*` node the plain spelling puts
7520                // directly under `field`. Unwrap it so both spellings reach the
7521                // same name/arguments split (#2196).
7522                let selected = match field.kind() {
7523                    "dependent_name" => field.named_child(0).unwrap_or(field),
7524                    _ => field,
7525                };
7526                let selected_name = match selected.kind() {
7527                    "template_method" | "template_function" | "template_type" => {
7528                        selected.child_by_field_name("name").unwrap_or(selected)
7529                    }
7530                    _ => selected,
7531                };
7532                if node_is_within(selected_name, candidate) {
7533                    return true;
7534                }
7535                // A template argument is structurally inside the field subtree,
7536                // but it is an independent reference rather than the selected
7537                // member name.
7538                node = parent;
7539                continue;
7540            }
7541            let receiver = parent
7542                .child_by_field_name("argument")
7543                .or_else(|| parent.child_by_field_name("object"))
7544                .or_else(|| parent.named_child(0));
7545            if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
7546                // Unknown grammar shape inside a field expression: fail closed
7547                // rather than treating it as a receiver reference.
7548                return true;
7549            }
7550        }
7551        node = parent;
7552    }
7553    false
7554}
7555
7556fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
7557    parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
7558}
7559
7560fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7561    let text = node_text(node, ctx.source);
7562    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
7563        return false;
7564    }
7565    if text.contains("::") {
7566        return ctx.visibility.contains_named_symbol(
7567            ctx.file,
7568            text,
7569            TargetKind::GlobalField,
7570            &ctx.spec.target,
7571        );
7572    }
7573    if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
7574        && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
7575    {
7576        return ctx.visibility.contains_named_symbol(
7577            ctx.file,
7578            text,
7579            TargetKind::GlobalField,
7580            &ctx.spec.target,
7581        );
7582    }
7583    if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
7584        && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
7585            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7586                brokk_bifrost_core::analyzer::Language::Cpp,
7587                &namespace,
7588            ) == indexed_scope
7589        })
7590    {
7591        return ctx.visibility.contains_named_symbol(
7592            ctx.file,
7593            text,
7594            TargetKind::GlobalField,
7595            &ctx.spec.target,
7596        );
7597    }
7598    bare_global_field_uniquely_resolves_to_target(text, ctx)
7599}
7600
7601fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
7602    let mut matched_target = false;
7603    for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
7604        if !has_persisted_global_field_identity(unit)
7605            || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
7606        {
7607            continue;
7608        }
7609        if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
7610            continue;
7611        }
7612        if same_visible_global_field_symbol(
7613            &ctx.analyzer,
7614            &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
7615            unit,
7616            &ctx.spec.target,
7617        ) {
7618            matched_target = true;
7619        } else {
7620            return false;
7621        }
7622    }
7623    matched_target
7624}
7625
7626fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
7627    // C++ type members persist their owner in `short_name` (`Owner.member`), while namespace
7628    // identity lives in `package_name`; global and namespace-scoped fields therefore have a
7629    // terminal-only short name. Keep this hot lookup projection-only instead of asking the
7630    // analyzer for every same-named candidate's parent.
7631    unit.is_field() && !unit.short_name().contains('.')
7632}
7633
7634fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7635    let text = node_text(node, ctx.source);
7636    if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
7637        return true;
7638    }
7639    if text.contains("::") {
7640        return ctx.visibility.resolve_known_non_target(
7641            ctx.file,
7642            text,
7643            TargetKind::GlobalField,
7644            &ctx.spec.target,
7645        );
7646    }
7647    let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
7648        return false;
7649    };
7650    cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
7651        && ctx
7652            .visibility
7653            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7654            .any(|unit| {
7655                has_persisted_global_field_identity(unit)
7656                    && unit.identifier() == ctx.spec.member_name
7657                    && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
7658                    && !same_visible_global_field_symbol(
7659                        &ctx.analyzer,
7660                        &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
7661                        unit,
7662                        &ctx.spec.target,
7663                    )
7664            })
7665}
7666
7667fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7668    if node.kind() == "field_expression" {
7669        let Some(field) = node.child_by_field_name("field") else {
7670            return;
7671        };
7672        if node_text(field, ctx.source) != ctx.spec.member_name {
7673            return;
7674        }
7675        *ctx.raw_match_count += 1;
7676        let receiver = node
7677            .child_by_field_name("argument")
7678            .or_else(|| node.child_by_field_name("object"));
7679        match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx)) {
7680            Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
7681            Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
7682            Some(
7683                MethodReceiverTargetResolution::NonTarget
7684                | MethodReceiverTargetResolution::Ambiguous,
7685            ) => {}
7686        }
7687        return;
7688    }
7689
7690    if matches!(node.kind(), "identifier" | "field_identifier")
7691        && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7692        && let Some(designator_owner) =
7693            designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
7694    {
7695        *ctx.raw_match_count += 1;
7696        match designator_owner {
7697            DesignatedInitializerOwner::Resolved(owner)
7698                if ctx
7699                    .spec
7700                    .owner
7701                    .as_ref()
7702                    .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
7703            {
7704                push_hit(node, ctx);
7705            }
7706            DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
7707            DesignatedInitializerOwner::Resolved(_) => {}
7708        }
7709        return;
7710    }
7711
7712    let qualified_member_name_matches =
7713        matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7714            && cpp_name_component_nodes(node)
7715                .and_then(|components| components.last().copied())
7716                .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
7717    if !matches!(
7718        node.kind(),
7719        "identifier"
7720            | "field_identifier"
7721            | "qualified_identifier"
7722            | "scoped_identifier"
7723            | "type_identifier"
7724    ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7725        && !qualified_member_name_matches)
7726        || !scan_leaf_is_value_position(node, ctx)
7727        || is_declaration_name(node)
7728        || is_member_field_own_declarator(node, ctx)
7729        || is_selected_field_expression_member_descendant(node)
7730        || is_nested_in_qualified_identifier(node)
7731    {
7732        return;
7733    }
7734    *ctx.raw_match_count += 1;
7735    if is_structurally_qualified(node) {
7736        match qualified_owner_resolution(node, ctx) {
7737            QualifiedOwnerResolution::Target => push_hit(node, ctx),
7738            QualifiedOwnerResolution::NonTarget => {}
7739            QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
7740        }
7741        return;
7742    }
7743    let text = node_text(node, ctx.source);
7744    if ctx.local_shadows.is_shadowed(text) {
7745        return;
7746    }
7747    let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
7748        && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
7749    let owner_context = structured_owner_context_resolution(node, ctx);
7750    if matches!(
7751        owner_context,
7752        StructuredOwnerContextResolution::SelfTarget
7753            | StructuredOwnerContextResolution::InheritedTarget
7754    ) || unscoped_enum_match
7755    {
7756        push_hit(node, ctx);
7757    } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
7758        .then_some(ctx.spec.owner.as_ref())
7759        .flatten()
7760    {
7761        let resolution =
7762            match resolve_active_using_enum_member(node, ctx) {
7763                ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
7764                ActiveUsingEnumMemberResolution::Class(resolution) => {
7765                    if direct_class_member_shadows(node, ctx) {
7766                        return;
7767                    }
7768                    resolution
7769                }
7770                ActiveUsingEnumMemberResolution::Namespace(resolution) => {
7771                    if let Some(owner) = structured_enclosing_owner(node, ctx) {
7772                        if direct_class_member_shadows(node, ctx) {
7773                            return;
7774                        }
7775                        let complete_same_file_leaf =
7776                            owner.source() == ctx.file
7777                                && ctx.analyzer.type_hierarchy_provider().is_some_and(
7778                                    |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
7779                                );
7780                        if !complete_same_file_leaf {
7781                            push_unproven_hit(node, ctx);
7782                            return;
7783                        }
7784                    }
7785                    match owner_context {
7786                        StructuredOwnerContextResolution::SelfTarget
7787                        | StructuredOwnerContextResolution::InheritedTarget
7788                        | StructuredOwnerContextResolution::NonTarget => return,
7789                        StructuredOwnerContextResolution::Ambiguous => {
7790                            push_unproven_hit(node, ctx);
7791                            return;
7792                        }
7793                        StructuredOwnerContextResolution::Missing => {}
7794                    }
7795                    if namespace_value_shadows(node, ctx) {
7796                        return;
7797                    }
7798                    resolution
7799                }
7800                ActiveUsingEnumMemberResolution::Missing => {
7801                    if direct_class_member_shadows(node, ctx)
7802                        || (structured_enclosing_owner(node, ctx).is_none()
7803                            && namespace_value_shadows(node, ctx))
7804                    {
7805                        return;
7806                    }
7807                    UsingEnumMemberResolution::Missing
7808                }
7809            };
7810        match resolution {
7811            UsingEnumMemberResolution::Resolved { owner, member }
7812                if same_visible_symbol(&owner, target_owner)
7813                    && same_visible_symbol(&member, &ctx.spec.target) =>
7814            {
7815                push_hit(node, ctx);
7816            }
7817            UsingEnumMemberResolution::Resolved { .. } => {}
7818            UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
7819                push_unproven_hit(node, ctx)
7820            }
7821        }
7822    } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
7823        push_unproven_hit(node, ctx);
7824    }
7825}
7826
7827enum ActiveUsingEnumMemberResolution {
7828    Block(UsingEnumMemberResolution),
7829    Class(UsingEnumMemberResolution),
7830    Namespace(UsingEnumMemberResolution),
7831    Missing,
7832}
7833
7834fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7835    structured_enclosing_owner(node, ctx).is_some_and(|owner| {
7836        ctx.visibility
7837            .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
7838            .into_iter()
7839            .next()
7840            .is_some()
7841    })
7842}
7843
7844fn resolve_active_using_enum_member(
7845    node: Node<'_>,
7846    ctx: &ScanCtx<'_>,
7847) -> ActiveUsingEnumMemberResolution {
7848    let block =
7849        ctx.using_enum_owners
7850            .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
7851    if !matches!(block, UsingEnumMemberResolution::Missing) {
7852        return ActiveUsingEnumMemberResolution::Block(block);
7853    }
7854    let class = structured_enclosing_owner(node, ctx);
7855    let namespace = enclosing_namespace_components(node, ctx.source);
7856    match ctx.semantic_using_enum_owners.resolve_member(
7857        ctx.visibility,
7858        ctx.file,
7859        class.as_ref(),
7860        &namespace,
7861        node.start_byte(),
7862        &ctx.spec.member_name,
7863    ) {
7864        SemanticUsingEnumMemberResolution::Class(resolution) => {
7865            ActiveUsingEnumMemberResolution::Class(resolution)
7866        }
7867        SemanticUsingEnumMemberResolution::Namespace(resolution) => {
7868            ActiveUsingEnumMemberResolution::Namespace(resolution)
7869        }
7870        SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
7871    }
7872}
7873
7874fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7875    let namespace = enclosing_namespace_components(node, ctx.source).join("::");
7876    !matches!(
7877        resolve_namespace_value(
7878            &ctx.analyzer,
7879            ctx.visibility,
7880            ctx.file,
7881            &namespace,
7882            &ctx.spec.member_name,
7883            node.start_byte(),
7884        ),
7885        NamespaceValueResolution::Missing
7886    )
7887}
7888
7889/// Whether `node` is a component of an enclosing qualified identifier's own
7890/// `scope`/`name` path, so the outer `qualified_identifier` is the single
7891/// reference surfaced for it.
7892///
7893/// A qualified identifier owns only that path. Everything else beneath it is an
7894/// independent reference that its own target scanner must resolve:
7895///
7896/// - `Owner::Template<argument>` holds each template argument outside the path.
7897/// - Error recovery can hang a complete member initializer off an `ERROR` child
7898///   of a synthetic qualified identifier, where no structured path exists.
7899/// - The grammar accepts a `pointer_type_declarator` as a qualified
7900///   identifier's `name`, so `EXPORT_MACRO Result *fn(unsigned int len = kMax);`
7901///   folds a whole parameter list under one `qualified_identifier`. Parameter
7902///   default values there are ordinary value expressions (#2548).
7903///
7904/// Follow the structured `scope`/`name` links rather than every ancestor, which
7905/// covers all three cases with one rule.
7906fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
7907    if node.kind() == "qualified_identifier" {
7908        return false;
7909    }
7910    let mut current = node;
7911    while let Some(parent) = current.parent() {
7912        let on_name_path = ["scope", "name"].into_iter().any(|field| {
7913            parent
7914                .child_by_field_name(field)
7915                .is_some_and(|component| same_node(component, current))
7916        });
7917        if !on_name_path {
7918            return false;
7919        }
7920        if parent.kind() == "qualified_identifier" {
7921            return true;
7922        }
7923        current = parent;
7924    }
7925    false
7926}
7927
7928fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
7929    receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
7930}
7931
7932fn receiver_type_units_with_budget(
7933    node: Node<'_>,
7934    source: &str,
7935    ctx: &ScanCtx<'_>,
7936    remaining_call_depth: usize,
7937) -> Vec<CodeUnit> {
7938    let mut current = node;
7939    let mut member_chain = Vec::new();
7940    let mut base_units = loop {
7941        match current.kind() {
7942            "field_expression" => {
7943                let Some(member) = current.child_by_field_name("field") else {
7944                    return Vec::new();
7945                };
7946                let Some(receiver) = current
7947                    .child_by_field_name("argument")
7948                    .or_else(|| current.child_by_field_name("object"))
7949                    .or_else(|| current.named_child(0))
7950                else {
7951                    return Vec::new();
7952                };
7953                member_chain.push(node_text(member, source));
7954                current = receiver;
7955            }
7956            "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
7957                let Some(inner) = current
7958                    .child_by_field_name("argument")
7959                    .or_else(|| current.named_child(0))
7960                else {
7961                    return Vec::new();
7962                };
7963                current = inner;
7964            }
7965            // Tree-sitter uses `field_identifier` for an unqualified member
7966            // field when it appears as the base of another field expression
7967            // (`data_.as_chars()` / `prefix.edge`).  Resolve it through the
7968            // same structured binding and enclosing-owner paths as an
7969            // ordinary identifier; falling through to `resolve_type` would
7970            // treat the field name as a type and lose the receiver identity.
7971            "identifier" | "field_identifier" => {
7972                let name = node_text(current, source);
7973                let local = ctx.bindings.resolve_symbol(name);
7974                if let Some(bindings) = local.as_precise() {
7975                    break receiver_units_from_bindings(current, bindings, ctx);
7976                }
7977                if ctx.bindings.is_shadowed(name) {
7978                    return Vec::new();
7979                }
7980                let owner = structured_enclosing_owner(current, ctx)
7981                    .filter(CodeUnit::is_class)
7982                    .or_else(|| {
7983                        enclosing_context(current, ctx)
7984                            .owner
7985                            .filter(CodeUnit::is_class)
7986                    });
7987                if let Some(owner) = owner {
7988                    // The enclosing class is only the search root: an implicit
7989                    // member-field receiver can be declared on any base, so the
7990                    // declaring owner comes from the same hierarchy walk the
7991                    // member chain below uses, not from an exact-parent match.
7992                    let declaring_owner = match resolve_declaring_member_owner(
7993                        &ctx.analyzer,
7994                        ctx.visibility,
7995                        ctx.file,
7996                        &owner,
7997                        name,
7998                    ) {
7999                        EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8000                        EnclosingMemberOwnerResolution::Missing => None,
8001                        EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8002                    };
8003                    if let Some(declaring_owner) = declaring_owner {
8004                        let implicit_fields = ctx
8005                            .visibility
8006                            .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8007                            .into_iter()
8008                            .filter(|unit| unit.is_field())
8009                            .collect::<Vec<_>>();
8010                        if !implicit_fields.is_empty() {
8011                            break receiver_units_from_declared_fields(
8012                                implicit_fields,
8013                                current,
8014                                ctx,
8015                            );
8016                        }
8017                    }
8018                }
8019                let global_fields = ctx
8020                    .visibility
8021                    .visible_identifier_candidates(ctx.file, name)
8022                    .filter(|unit| {
8023                        has_persisted_global_field_identity(unit) && unit.identifier() == name
8024                    })
8025                    .collect::<Vec<_>>();
8026                if global_fields.is_empty() {
8027                    break ctx
8028                        .visibility
8029                        .resolve_type(ctx.file, name)
8030                        .into_iter()
8031                        .collect();
8032                }
8033                if let Some(first) = global_fields.first()
8034                    && global_fields
8035                        .iter()
8036                        .skip(1)
8037                        .any(|field| !same_visible_symbol(first, field))
8038                {
8039                    return Vec::new();
8040                }
8041                break receiver_units_from_declared_fields(global_fields, current, ctx);
8042            }
8043            "call_expression" | "new_expression" => {
8044                break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8045                    .and_then(|binding| binding.unit)
8046                    .into_iter()
8047                    .collect();
8048            }
8049            "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8050                let name = node_text(current, source);
8051                let local = ctx.bindings.resolve_symbol(name);
8052                if let Some(bindings) = local.as_precise() {
8053                    break receiver_units_from_bindings(current, bindings, ctx);
8054                }
8055                return Vec::new();
8056            }
8057            "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8058            "qualified_identifier" | "scoped_identifier" => {
8059                let reference = node_text(current, source);
8060                let fields = ctx
8061                    .visibility
8062                    .named_candidates(ctx.file, reference, TargetKind::GlobalField)
8063                    .into_iter()
8064                    .filter(has_persisted_global_field_identity)
8065                    .collect::<Vec<_>>();
8066                if fields.is_empty() {
8067                    break ctx
8068                        .visibility
8069                        .resolve_type(ctx.file, reference)
8070                        .into_iter()
8071                        .collect();
8072                }
8073                break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
8074            }
8075            _ => {
8076                break ctx
8077                    .visibility
8078                    .resolve_type(ctx.file, node_text(current, source))
8079                    .into_iter()
8080                    .collect();
8081            }
8082        }
8083    };
8084
8085    base_units = canonical_receiver_units(base_units, ctx);
8086    if base_units.is_empty() {
8087        return Vec::new();
8088    }
8089
8090    while let Some(member_name) = member_chain.pop() {
8091        let mut next_units = Vec::new();
8092        for owner in &base_units {
8093            let declaring_owner = match resolve_declaring_member_owner(
8094                &ctx.analyzer,
8095                ctx.visibility,
8096                ctx.file,
8097                owner,
8098                member_name,
8099            ) {
8100                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8101                EnclosingMemberOwnerResolution::Missing => continue,
8102                EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8103            };
8104            let fields = ctx.visibility.visible_members_for_owner_name(
8105                ctx.file,
8106                &declaring_owner,
8107                member_name,
8108            );
8109            for field in fields.into_iter().filter(|unit| unit.is_field()) {
8110                let Some(unit) =
8111                    field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8112                        .and_then(|binding| binding.unit)
8113                        .or_else(|| recovered_receiver_field_type(current, field, ctx))
8114                else {
8115                    continue;
8116                };
8117                if !next_units
8118                    .iter()
8119                    .any(|existing| same_visible_symbol(existing, &unit))
8120                {
8121                    next_units.push(unit);
8122                }
8123            }
8124        }
8125        if next_units.is_empty() {
8126            return Vec::new();
8127        }
8128        base_units = unanimous_receiver_units(next_units);
8129        if base_units.is_empty() {
8130            return Vec::new();
8131        }
8132    }
8133    base_units
8134}
8135
8136fn receiver_units_from_bindings(
8137    node: Node<'_>,
8138    bindings: &HashSet<CppScanBinding>,
8139    ctx: &ScanCtx<'_>,
8140) -> Vec<CodeUnit> {
8141    let mut units = Vec::new();
8142    for binding in bindings {
8143        let raw_unit = if let Some(unit) = &binding.unit {
8144            unit.clone()
8145        } else {
8146            let Some(type_name) = binding.type_name.as_deref() else {
8147                return Vec::new();
8148            };
8149            let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
8150                return Vec::new();
8151            };
8152            unit
8153        };
8154        if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
8155            // Type aliases are represented as class units. When an alias has
8156            // a dependent target, canonicalization deliberately preserves
8157            // that alias rather than inventing a concrete class. Give the
8158            // existing target-guided structured recovery a chance to prove
8159            // the queried owner before accepting that unresolved identity.
8160            if same_visible_symbol(&unit, &raw_unit)
8161                && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
8162            {
8163                units.push(recovered);
8164                continue;
8165            }
8166            units.push(unit);
8167            continue;
8168        }
8169        if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
8170            units.push(unit);
8171            continue;
8172        }
8173        return Vec::new();
8174    }
8175    unanimous_receiver_units(units)
8176}
8177
8178/// Resolve a using-alias receiver from its declaration's structured RHS when
8179/// the alias target index cannot cross a malformed namespace-sentinel node.
8180/// The inverse target owner supplies only the exact class identity to prove;
8181/// lexical AST resolution still decides whether the alias denotes that class.
8182fn recovered_receiver_alias_target(
8183    reference: Node<'_>,
8184    alias: &CodeUnit,
8185    ctx: &ScanCtx<'_>,
8186) -> Option<CodeUnit> {
8187    if !ctx
8188        .analyzer
8189        .type_alias_provider()
8190        .is_some_and(|provider| provider.is_type_alias(alias))
8191    {
8192        return None;
8193    }
8194    let target = ctx.spec.owner.as_ref()?.clone();
8195    if !target.is_class() || alias.source() != ctx.file {
8196        return None;
8197    }
8198    let range = ctx
8199        .analyzer
8200        .ranges(alias)
8201        .into_iter()
8202        .find(|range| range.start_byte < range.end_byte)?;
8203    let mut node =
8204        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8205    while !matches!(node.kind(), "alias_declaration" | "type_definition") {
8206        node = node.parent()?;
8207    }
8208    let type_descriptor = node.child_by_field_name("type")?;
8209    let type_node = receiver_type_node_base(type_descriptor);
8210    let resolution = resolve_type_node_lexically_for_target(
8211        type_node,
8212        &ctx.analyzer,
8213        ctx.visibility,
8214        &ctx.ordinary_type_imports,
8215        ctx.file,
8216        ctx.source,
8217        &target,
8218        Some(&ctx.lexical_scope_cache),
8219        ctx.recovered_sentinel_scope(type_node).as_deref(),
8220    );
8221    if let LexicalTypeResolution::Resolved {
8222        unit, candidates, ..
8223    } = resolution
8224        && (same_visible_symbol(&unit, &target)
8225            || candidates
8226                .iter()
8227                .any(|candidate| same_visible_symbol(candidate, &target)))
8228    {
8229        return Some(target);
8230    }
8231    let (components, global) = type_reference_components(type_node, ctx.source)?;
8232    if !global
8233        && components.len() == 2
8234        && cpp_active_template_type_parameter(
8235            type_node,
8236            &components[0],
8237            ctx.source,
8238            &ParentIndex::unindexed(),
8239        )
8240    {
8241        let alias_provider = ctx.analyzer.type_alias_provider()?;
8242        let concrete = ctx
8243            .visibility
8244            .visible_identifier_candidates(ctx.file, &components[1])
8245            .filter(|candidate| {
8246                alias_provider.is_type_alias(candidate)
8247                    && !same_visible_symbol(candidate, alias)
8248                    && type_owner_of(&ctx.analyzer, candidate).is_some_and(|owner| owner.is_class())
8249                    && ctx.visibility.is_physically_visible(ctx.file, candidate)
8250                    && ctx
8251                        .visibility
8252                        .external_type_candidate_guard_compatible_in_context(
8253                            &ctx.analyzer,
8254                            ctx.file,
8255                            candidate,
8256                            type_node,
8257                        )
8258            })
8259            .filter_map(|candidate| {
8260                let canonical = ctx.visibility.canonical_visible_full_type_unit(
8261                    &ctx.analyzer,
8262                    ctx.file,
8263                    candidate,
8264                )?;
8265                // Another dependent alias can have the same nested name but
8266                // still canonicalize only to itself. It supplies no concrete
8267                // receiver identity and therefore cannot compete with an
8268                // alias that reaches an indexed class.
8269                (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
8270            })
8271            .collect::<Vec<_>>();
8272        if let [unit] = unanimous_receiver_units(concrete).as_slice()
8273            && same_visible_symbol(unit, &target)
8274        {
8275            return Some(target);
8276        }
8277    }
8278    let scope = ctx
8279        .recovered_sentinel_scope(type_node)
8280        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8281    let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
8282    let visible = ctx.visibility.external_type_candidate_visible_in_context(
8283        &ctx.analyzer,
8284        ctx.file,
8285        &target,
8286        type_node,
8287    );
8288    (path_matches && visible).then_some(target)
8289}
8290
8291fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8292    let normalized = normalize_cpp_type_name(type_name);
8293    if normalized.is_empty() {
8294        return None;
8295    }
8296
8297    // A function-local alias is intentionally absent from the visibility
8298    // index. Recover its RHS from the structured alias declaration before
8299    // trying file-visible type lookup; this keeps the alias's lexical shadow
8300    // boundary intact.
8301    if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
8302        let alias_type = receiver_type_node_base(alias_type);
8303        match ctx
8304            .visibility
8305            .resolve_type_node_result(ctx.file, alias_type, ctx.source)
8306        {
8307            Ok(Some(unit)) => return Some(unit),
8308            Err(_) => return None,
8309            Ok(None) => {}
8310        }
8311        if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
8312            return Some(unit);
8313        }
8314    }
8315
8316    match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
8317        LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
8318        LexicalTypeResolution::Ambiguous => return None,
8319        LexicalTypeResolution::Missing => {}
8320    }
8321    let candidates = ctx
8322        .visibility
8323        .type_name_candidates(ctx.file, &normalized)
8324        .into_iter()
8325        .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
8326        .collect();
8327    unanimous_receiver_units(candidates).into_iter().next()
8328}
8329
8330fn resolve_receiver_type_node_lexically(
8331    type_node: Node<'_>,
8332    ctx: &ScanCtx<'_>,
8333) -> Option<CodeUnit> {
8334    let type_node = receiver_type_node_base(type_node);
8335    let components = cpp_type_name_components(type_node, ctx.source)?;
8336    let lexical_scope = match enclosing_lexical_scope_components(
8337        type_node,
8338        &ctx.analyzer,
8339        ctx.visibility,
8340        ctx.file,
8341        ctx.source,
8342    ) {
8343        LexicalScopeResolution::Resolved(scope) => scope,
8344        LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
8345    };
8346    match ctx.visibility.resolve_type_components_lexically(
8347        &ctx.analyzer,
8348        ctx.file,
8349        &components,
8350        is_globally_qualified_cpp_name(type_node),
8351        &lexical_scope,
8352    ) {
8353        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
8354        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
8355    }
8356}
8357
8358fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
8359    while matches!(node.kind(), "type_descriptor" | "dependent_type") {
8360        let Some(inner) = node.child_by_field_name("type").or_else(|| {
8361            if node.kind() == "dependent_type" {
8362                node.named_child(0)
8363            } else {
8364                None
8365            }
8366        }) else {
8367            break;
8368        };
8369        node = inner;
8370    }
8371    node
8372}
8373
8374fn resolve_receiver_type_name_lexically(
8375    node: Node<'_>,
8376    normalized: &str,
8377    ctx: &ScanCtx<'_>,
8378) -> LexicalTypeResolution {
8379    let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8380        brokk_bifrost_core::analyzer::Language::Cpp,
8381        normalized,
8382    );
8383    if components.is_empty() {
8384        return LexicalTypeResolution::Missing;
8385    }
8386    let lexical_scope = match enclosing_lexical_scope_components(
8387        node,
8388        &ctx.analyzer,
8389        ctx.visibility,
8390        ctx.file,
8391        ctx.source,
8392    ) {
8393        LexicalScopeResolution::Resolved(scope) => scope,
8394        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
8395        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
8396    };
8397    ctx.visibility.resolve_type_components_lexically(
8398        &ctx.analyzer,
8399        ctx.file,
8400        &components,
8401        normalized.starts_with("::"),
8402        &lexical_scope,
8403    )
8404}
8405
8406fn local_receiver_alias_type_node<'tree>(
8407    node: Node<'tree>,
8408    name: &str,
8409    ctx: &ScanCtx<'_>,
8410) -> Option<Node<'tree>> {
8411    let callable = nearest_callable_scope(node)?;
8412    let mut root_callable = callable;
8413    let mut ancestor = callable.parent();
8414    while let Some(current) = ancestor {
8415        if matches!(current.kind(), "function_definition" | "lambda_expression") {
8416            root_callable = current;
8417        }
8418        ancestor = current.parent();
8419    }
8420
8421    let mut stack = vec![root_callable];
8422    let mut best = None;
8423    while let Some(current) = stack.pop() {
8424        if current.start_byte() >= node.start_byte() {
8425            continue;
8426        }
8427        if local_type_alias_name_node(current)
8428            .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
8429            && local_alias_scope_contains_node(current, node)
8430        {
8431            let replace = best
8432                .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
8433            if replace {
8434                best = current.child_by_field_name("type");
8435            }
8436        }
8437        let mut cursor = current.walk();
8438        stack.extend(current.named_children(&mut cursor));
8439    }
8440    best
8441}
8442
8443fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8444    let mut canonical = Vec::with_capacity(units.len());
8445    for unit in units {
8446        let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
8447            return Vec::new();
8448        };
8449        canonical.push(unit);
8450    }
8451    unanimous_receiver_units(canonical)
8452}
8453
8454fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8455    if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
8456        return cached.clone();
8457    }
8458    let canonical = ctx
8459        .visibility
8460        .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
8461    ctx.receiver_canonical_type_cache
8462        .borrow_mut()
8463        .insert(unit.clone(), canonical.clone());
8464    canonical
8465}
8466
8467fn receiver_units_from_declared_fields(
8468    fields: Vec<&CodeUnit>,
8469    reference: Node<'_>,
8470    ctx: &ScanCtx<'_>,
8471) -> Vec<CodeUnit> {
8472    let Some(first) = fields.first() else {
8473        return Vec::new();
8474    };
8475    if fields
8476        .iter()
8477        .skip(1)
8478        .any(|field| !same_visible_symbol(first, field))
8479    {
8480        return Vec::new();
8481    }
8482    unanimous_receiver_units(
8483        fields
8484            .into_iter()
8485            .filter_map(|field| {
8486                field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8487                    .and_then(|binding| binding.unit)
8488                    .or_else(|| recovered_receiver_field_type(reference, field, ctx))
8489            })
8490            .collect(),
8491    )
8492}
8493
8494/// Resolve a field receiver's declared type from its structured declaration
8495/// when the persisted type fact was built under a malformed sentinel scope.
8496/// The queried member owner supplies the exact class identity to prove; the
8497/// declaration's type node and recovered lexical path provide the evidence.
8498fn recovered_receiver_field_type(
8499    reference: Node<'_>,
8500    field: &CodeUnit,
8501    ctx: &ScanCtx<'_>,
8502) -> Option<CodeUnit> {
8503    let target = ctx.spec.owner.as_ref()?.clone();
8504    if !target.is_class() || field.source() != ctx.file {
8505        return None;
8506    }
8507    let range = ctx
8508        .analyzer
8509        .ranges(field)
8510        .into_iter()
8511        .find(|range| range.start_byte < range.end_byte)?;
8512    let mut declaration =
8513        root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8514    while !matches!(declaration.kind(), "declaration" | "field_declaration") {
8515        declaration = declaration.parent()?;
8516    }
8517    let type_node = first_type_child(declaration)?;
8518    let resolution = resolve_type_node_lexically_for_target(
8519        type_node,
8520        &ctx.analyzer,
8521        ctx.visibility,
8522        &ctx.ordinary_type_imports,
8523        ctx.file,
8524        ctx.source,
8525        &target,
8526        Some(&ctx.lexical_scope_cache),
8527        ctx.recovered_sentinel_scope(type_node).as_deref(),
8528    );
8529    if let LexicalTypeResolution::Resolved {
8530        unit, candidates, ..
8531    } = resolution
8532        && (same_visible_symbol(&unit, &target)
8533            || candidates
8534                .iter()
8535                .any(|candidate| same_visible_symbol(candidate, &target)))
8536    {
8537        return Some(target);
8538    }
8539    let type_node = receiver_type_node_base(type_node);
8540    let (components, global) = type_reference_components(type_node, ctx.source)?;
8541    let scope = ctx
8542        .recovered_sentinel_scope(type_node)
8543        .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8544    (indexed_scope_matches_target_name(&scope, &components, global, &target)
8545        && ctx.visibility.external_type_candidate_visible_in_context(
8546            &ctx.analyzer,
8547            ctx.file,
8548            &target,
8549            type_node,
8550        ))
8551    .then_some(target)
8552}
8553
8554fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
8555    let mut unique = Vec::new();
8556    for unit in units {
8557        if !unique
8558            .iter()
8559            .any(|existing| same_visible_symbol(existing, &unit))
8560        {
8561            unique.push(unit);
8562            if unique.len() > 1 {
8563                return Vec::new();
8564            }
8565        }
8566    }
8567    unique
8568}
8569
8570fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8571    let Some(owner) = ctx.spec.owner.as_ref() else {
8572        return false;
8573    };
8574    match node.kind() {
8575        // `declaring_owner_for_explicit_receiver` already tried the complete field chain.
8576        // When that structured lookup fails, the base expression's owner is not the field's
8577        // type: treating `value_` as `Value` would misclassify
8578        // `value_.map_->clear()` as `Value::clear`. Direct `this->clear()` reaches this helper
8579        // with the `this` node itself, so failing closed for an unresolved field chain does not
8580        // discard genuine self calls.
8581        "field_expression" => false,
8582        "call_expression" => node
8583            .child_by_field_name("function")
8584            .is_some_and(|function| receiver_matches_target(function, ctx)),
8585        "pointer_expression" | "parenthesized_expression" => node
8586            .child_by_field_name("argument")
8587            .or_else(|| node.named_child(0))
8588            .is_some_and(|child| receiver_matches_target(child, ctx)),
8589        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
8590            .bindings
8591            .resolve_symbol(node_text(node, ctx.source))
8592            .as_precise()
8593            .is_some_and(|targets| {
8594                targets
8595                    .iter()
8596                    .filter_map(|target| target.unit.as_ref())
8597                    .any(|target| {
8598                        receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
8599                    })
8600            }),
8601        "this" => same_owner_context(node, ctx),
8602        _ => qualified_owner_matches(node, ctx),
8603    }
8604}
8605
8606fn declaring_owner_for_explicit_receiver(
8607    receiver: Node<'_>,
8608    call_arity: Option<usize>,
8609    ctx: &ScanCtx<'_>,
8610) -> EnclosingMemberOwnerResolution {
8611    if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
8612        return EnclosingMemberOwnerResolution::Missing;
8613    }
8614    declaring_owner_from_receiver_units(
8615        receiver_type_units(receiver, ctx.source, ctx),
8616        receiver.start_byte(),
8617        call_arity,
8618        ctx,
8619    )
8620}
8621
8622/// The owner that declares the queried member for a receiver already typed.
8623///
8624/// `reference_byte` is the reference's position in the scanned file, which
8625/// decides which declarations are visible there. A receiver recovered from a
8626/// macro replacement is typed against the sentinel parse but still reported at
8627/// its bytes in the defining file, so the two arrive separately.
8628fn declaring_owner_from_receiver_units(
8629    receiver_units: Vec<CodeUnit>,
8630    reference_byte: usize,
8631    call_arity: Option<usize>,
8632    ctx: &ScanCtx<'_>,
8633) -> EnclosingMemberOwnerResolution {
8634    let mut declaring_owner = None;
8635    for receiver_owner in receiver_units {
8636        if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
8637            receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
8638        }) {
8639            if declaring_owner
8640                .as_ref()
8641                .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
8642            {
8643                return EnclosingMemberOwnerResolution::Ambiguous;
8644            }
8645            declaring_owner = Some(receiver_owner);
8646            continue;
8647        }
8648        let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
8649        let owner_resolution = match call_arity {
8650            Some(arity) => resolve_declaring_callable_owner(
8651                &ctx.analyzer,
8652                ctx.visibility,
8653                ctx.file,
8654                ordinary,
8655                &ctx.spec.member_name,
8656                arity,
8657            ),
8658            None => ordinary,
8659        };
8660        match owner_resolution {
8661            EnclosingMemberOwnerResolution::Owner(owner) => {
8662                if declaring_owner
8663                    .as_ref()
8664                    .is_some_and(|existing| !same_visible_symbol(existing, &owner))
8665                {
8666                    return EnclosingMemberOwnerResolution::Ambiguous;
8667                }
8668                declaring_owner = Some(owner);
8669            }
8670            EnclosingMemberOwnerResolution::Ambiguous => {
8671                return EnclosingMemberOwnerResolution::Ambiguous;
8672            }
8673            EnclosingMemberOwnerResolution::Missing => {}
8674        }
8675    }
8676    declaring_owner
8677        .map(EnclosingMemberOwnerResolution::Owner)
8678        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
8679}
8680
8681fn declaring_owner_from_call_function(
8682    function: Node<'_>,
8683    call_arity: Option<usize>,
8684    ctx: &ScanCtx<'_>,
8685) -> Option<EnclosingMemberOwnerResolution> {
8686    match function.kind() {
8687        "field_expression" => function
8688            .child_by_field_name("argument")
8689            .or_else(|| function.child_by_field_name("object"))
8690            .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
8691            .or(Some(EnclosingMemberOwnerResolution::Missing)),
8692        "call_expression" => function
8693            .child_by_field_name("function")
8694            .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
8695        _ => None,
8696    }
8697}
8698
8699enum MethodReceiverTargetResolution {
8700    Target,
8701    NonTarget,
8702    Ambiguous,
8703    Missing,
8704}
8705
8706fn method_receiver_target_resolution(
8707    node: Node<'_>,
8708    declaring_owner: EnclosingMemberOwnerResolution,
8709    ctx: &ScanCtx<'_>,
8710) -> MethodReceiverTargetResolution {
8711    match declaring_owner {
8712        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
8713            declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
8714        }
8715        EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
8716            MethodReceiverTargetResolution::Missing
8717        }
8718        EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
8719            MethodReceiverTargetResolution::Target
8720        }
8721        EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
8722            MethodReceiverTargetResolution::NonTarget
8723        }
8724        EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
8725    }
8726}
8727
8728/// Decide the queried member against a receiver's declaring owner alone.
8729///
8730/// This is the part of receiver typing that needs nothing but the owner and
8731/// the reference's position, so a member call recovered from a macro
8732/// replacement reaches the same verdict as ordinary code even though its
8733/// syntax lives in a separate sentinel parse.
8734fn declaring_owner_target_resolution(
8735    declaring_owner: EnclosingMemberOwnerResolution,
8736    reference_byte: usize,
8737    ctx: &ScanCtx<'_>,
8738) -> MethodReceiverTargetResolution {
8739    let Some(target_owner) = ctx.spec.owner.as_ref() else {
8740        return MethodReceiverTargetResolution::Missing;
8741    };
8742    match declaring_owner {
8743        EnclosingMemberOwnerResolution::Owner(owner)
8744            if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
8745        {
8746            MethodReceiverTargetResolution::Target
8747        }
8748        EnclosingMemberOwnerResolution::Owner(owner)
8749            if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
8750        {
8751            MethodReceiverTargetResolution::NonTarget
8752        }
8753        EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
8754            MethodReceiverTargetResolution::Missing
8755        }
8756        EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
8757    }
8758}
8759
8760fn explicit_receiver_target_resolution(
8761    receiver: Node<'_>,
8762    call_arity: Option<usize>,
8763    ctx: &ScanCtx<'_>,
8764) -> MethodReceiverTargetResolution {
8765    method_receiver_target_resolution(
8766        receiver,
8767        declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
8768        ctx,
8769    )
8770}
8771
8772fn call_function_target_resolution(
8773    function: Node<'_>,
8774    ctx: &ScanCtx<'_>,
8775) -> MethodReceiverTargetResolution {
8776    let call_arity = function.parent().and_then(|call| {
8777        (call.kind() == "call_expression")
8778            .then(|| {
8779                ctx.visibility
8780                    .call_arity_evidence(ctx.file, call, ctx.source)
8781                    .exact()
8782            })
8783            .flatten()
8784    });
8785    let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
8786    else {
8787        // A bare function identifier has an implicit receiver. Do not reinterpret
8788        // that identifier as a same-named type or value before enclosing-owner
8789        // lookup gets a chance to establish the member call.
8790        return MethodReceiverTargetResolution::Missing;
8791    };
8792    method_receiver_target_resolution(function, declaring_owner, ctx)
8793}
8794
8795fn receiver_owner_matches_target(
8796    receiver_owner: &CodeUnit,
8797    target_owner: &CodeUnit,
8798    reference_byte: usize,
8799    ctx: &ScanCtx<'_>,
8800) -> bool {
8801    same_symbol(receiver_owner, target_owner)
8802        || same_logical_symbol(receiver_owner, target_owner)
8803            && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
8804                || (ctx.spec.owner_is_forward_declaration
8805                    && ctx
8806                        .visibility
8807                        .is_physically_visible(ctx.file, receiver_owner))
8808                || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
8809                || target_group_contains_owner_peer(receiver_owner, ctx))
8810}
8811
8812fn receiver_owner_is_known_non_target(
8813    receiver_owner: &CodeUnit,
8814    target_owner: &CodeUnit,
8815    reference_byte: usize,
8816    ctx: &ScanCtx<'_>,
8817) -> bool {
8818    if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
8819        return false;
8820    }
8821    if !same_logical_symbol(receiver_owner, target_owner) {
8822        return true;
8823    }
8824    !ctx.target_group.iter().any(|target| {
8825        same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
8826    })
8827}
8828
8829fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
8830    ctx.visibility
8831        .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
8832        && ctx.target_group.iter().any(|target| {
8833            type_owner_of(&ctx.analyzer, target)
8834                .as_ref()
8835                .is_some_and(|target_owner| {
8836                    same_symbol(target_owner, owner)
8837                        || (same_logical_symbol(target_owner, owner)
8838                            && target_owner.source() == owner.source())
8839                })
8840        })
8841}
8842
8843fn visible_target_peer_matches_owner(
8844    owner: &CodeUnit,
8845    reference_byte: usize,
8846    ctx: &ScanCtx<'_>,
8847) -> bool {
8848    ctx.visibility
8849        .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
8850        && ctx
8851            .visibility
8852            .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8853            .any(|candidate| {
8854                cpp_callable_definitions_share_identity_evidence(
8855                    &ctx.analyzer,
8856                    candidate,
8857                    &ctx.spec.target,
8858                ) && ctx.visibility.declaration_visible_at(
8859                    &ctx.analyzer,
8860                    ctx.file,
8861                    candidate,
8862                    reference_byte,
8863                ) && type_owner_of(&ctx.analyzer, candidate)
8864                    .as_ref()
8865                    .is_some_and(|candidate_owner| {
8866                        same_symbol(candidate_owner, owner)
8867                            || (same_logical_symbol(candidate_owner, owner)
8868                                && candidate_owner.source() == owner.source())
8869                    })
8870            })
8871}
8872
8873/// Whether `node` is the implicit-object receiver of a member call.
8874///
8875/// `reference_is_c` is the compilation language the reference is read in
8876/// (#1970): `this` is the C++ implicit object only where C++ is what compiles
8877/// the source, and is an ordinary identifier in a `.c` file or a header every
8878/// reaching translation unit compiles as C.
8879fn receiver_is_self_like(node: Node<'_>, reference_is_c: bool) -> bool {
8880    match node.kind() {
8881        "this" => !reference_is_c,
8882        "pointer_expression" | "parenthesized_expression" => node
8883            .child_by_field_name("argument")
8884            .or_else(|| node.named_child(0))
8885            .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
8886        _ => false,
8887    }
8888}
8889
8890fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
8891    match function.kind() {
8892        "field_expression" => function
8893            .child_by_field_name("argument")
8894            .or_else(|| function.child_by_field_name("object"))
8895            .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
8896        _ => receiver_is_self_like(function, reference_is_c),
8897    }
8898}
8899
8900fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8901    let Some(owner) = ctx.spec.owner.as_ref() else {
8902        return false;
8903    };
8904    match node.kind() {
8905        "field_expression" => node
8906            .child_by_field_name("argument")
8907            .or_else(|| node.child_by_field_name("object"))
8908            .is_some_and(|receiver| {
8909                // `this` names the enclosing class, not necessarily the class that
8910                // declares the invoked member. Let the hierarchy-aware enclosing-owner
8911                // lookup decide whether this is a same-owner call, an inherited base
8912                // member, a nearer override, or an ambiguous base. Rejecting a derived
8913                // `this` merely because its immediate type differs from the target owner
8914                // drops genuine inherited calls (#2541).
8915                if receiver_is_self_like(
8916                    receiver,
8917                    ctx.analyzer.reference_uses_c_semantics(ctx.file),
8918                ) {
8919                    return false;
8920                }
8921                let units = receiver_type_units(receiver, ctx.source, ctx);
8922                !units.is_empty()
8923                    && units.iter().all(|target| {
8924                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
8925                    })
8926            }),
8927        "call_expression" => node
8928            .child_by_field_name("function")
8929            .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
8930        "pointer_expression" | "parenthesized_expression" => node
8931            .child_by_field_name("argument")
8932            .or_else(|| node.named_child(0))
8933            .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
8934        "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
8935            .bindings
8936            .resolve_symbol(node_text(node, ctx.source))
8937            .as_precise()
8938            .is_some_and(|targets| {
8939                let units = targets
8940                    .iter()
8941                    .filter_map(|target| target.unit.as_ref())
8942                    .collect::<Vec<_>>();
8943                !units.is_empty()
8944                    && units.iter().all(|target| {
8945                        receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
8946                    })
8947            }),
8948        "this" => known_non_target_owner_context(node, ctx),
8949        "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
8950            qualified_owner_is_known_non_target(node, ctx)
8951        }
8952        _ => false,
8953    }
8954}
8955
8956#[derive(Clone, Copy, PartialEq, Eq)]
8957enum QualifiedOwnerResolution {
8958    Target,
8959    NonTarget,
8960    Unresolved,
8961}
8962
8963#[derive(Clone)]
8964pub enum LexicalScopeResolution {
8965    Resolved(Vec<String>),
8966    Ambiguous,
8967    Missing,
8968}
8969
8970type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
8971
8972fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8973    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
8974}
8975
8976fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8977    qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
8978}
8979
8980fn is_structurally_qualified(node: Node<'_>) -> bool {
8981    matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8982        && qualified_name_has_concrete_scope_separators(node)
8983}
8984
8985fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
8986    let Some(target_owner) = ctx.spec.owner.as_ref() else {
8987        return QualifiedOwnerResolution::Unresolved;
8988    };
8989    let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
8990        return QualifiedOwnerResolution::Unresolved;
8991    };
8992    // A malformed wrapper can make the parser-derived enclosing owner look
8993    // like the lexical namespace (for example tinyxml2's macro-prefixed
8994    // XMLHandle declarations). Recover the target namespace only while this
8995    // function owns scope reconstruction. An explicit recovered scope is
8996    // authoritative and enters the scoped resolver directly.
8997    if !global
8998        && !matches!(
8999            enclosing_lexical_scope_components(
9000                node,
9001                &ctx.analyzer,
9002                ctx.visibility,
9003                ctx.file,
9004                ctx.source,
9005            ),
9006            LexicalScopeResolution::Resolved(_)
9007        )
9008    {
9009        return QualifiedOwnerResolution::Unresolved;
9010    }
9011    match resolve_type_components_lexically_at_for_target_with_scope_cache(
9012        node,
9013        &components,
9014        global,
9015        &ctx.analyzer,
9016        ctx.visibility,
9017        &ctx.ordinary_type_imports,
9018        ctx.file,
9019        ctx.source,
9020        target_owner,
9021        false,
9022        Some(&ctx.lexical_scope_cache),
9023    ) {
9024        LexicalTypeResolution::Resolved { unit: owner, .. } => {
9025            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
9026                return QualifiedOwnerResolution::Target;
9027            }
9028            match cached_declaring_member_owner(&owner, ctx) {
9029                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9030                    if receiver_owner_matches_target(
9031                        &declaring_owner,
9032                        target_owner,
9033                        node.start_byte(),
9034                        ctx,
9035                    ) =>
9036                {
9037                    QualifiedOwnerResolution::Target
9038                }
9039                EnclosingMemberOwnerResolution::Owner(declaring_owner)
9040                    if receiver_owner_is_known_non_target(
9041                        &declaring_owner,
9042                        target_owner,
9043                        node.start_byte(),
9044                        ctx,
9045                    ) =>
9046                {
9047                    QualifiedOwnerResolution::NonTarget
9048                }
9049                EnclosingMemberOwnerResolution::Owner(_)
9050                | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
9051                EnclosingMemberOwnerResolution::Missing
9052                    if same_visible_symbol(&owner, target_owner) =>
9053                {
9054                    QualifiedOwnerResolution::Unresolved
9055                }
9056                EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
9057            }
9058        }
9059        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
9060            QualifiedOwnerResolution::Unresolved
9061        }
9062    }
9063}
9064
9065fn qualified_callable_owner_components(
9066    node: Node<'_>,
9067    source: &str,
9068) -> Option<(Vec<String>, bool)> {
9069    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9070        || !qualified_name_has_concrete_scope_separators(node)
9071    {
9072        return None;
9073    }
9074    let global = is_globally_qualified_cpp_name(node);
9075    let mut components = Vec::new();
9076    append_cpp_name_components(node, source, &mut components)?;
9077    components.pop()?;
9078    (!components.is_empty()).then_some((components, global))
9079}
9080
9081fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
9082    if !matches!(
9083        node.kind(),
9084        "identifier"
9085            | "type_identifier"
9086            | "namespace_identifier"
9087            | "qualified_identifier"
9088            | "scoped_identifier"
9089            | "scoped_type_identifier"
9090            | "template_type"
9091            | "template_function"
9092    ) {
9093        return None;
9094    }
9095    let mut components = Vec::new();
9096    append_cpp_name_components(node, source, &mut components)?;
9097    (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
9098}
9099
9100pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
9101    let mut namespaces = Vec::new();
9102    let mut current = node.parent();
9103    while let Some(parent) = current {
9104        if parent.kind() == "namespace_definition"
9105            && let Some(name) = parent.child_by_field_name("name")
9106        {
9107            let mut components = Vec::new();
9108            if append_cpp_name_components(name, source, &mut components).is_some() {
9109                namespaces.push(components);
9110            }
9111        }
9112        current = parent.parent();
9113    }
9114    namespaces.reverse();
9115    namespaces.into_iter().flatten().collect()
9116}
9117
9118pub fn enclosing_lexical_scope_components(
9119    node: Node<'_>,
9120    analyzer: &CppGraphSource<'_>,
9121    visibility: &VisibilityIndex<'_>,
9122    file: &ProjectFile,
9123    source: &str,
9124) -> LexicalScopeResolution {
9125    enclosing_lexical_scope_components_with_unresolved_owner(
9126        node, analyzer, visibility, file, source, false, false,
9127    )
9128}
9129
9130#[allow(clippy::too_many_arguments)]
9131fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
9132    node: Node<'_>,
9133    analyzer: &CppGraphSource<'_>,
9134    visibility: &VisibilityIndex<'_>,
9135    file: &ProjectFile,
9136    source: &str,
9137    allow_structured_unresolved_owner: bool,
9138    ignore_function_owner: bool,
9139    cache: Option<&LexicalScopeCache>,
9140) -> LexicalScopeResolution {
9141    let Some(cache) = cache else {
9142        return enclosing_lexical_scope_components_with_unresolved_owner(
9143            node,
9144            analyzer,
9145            visibility,
9146            file,
9147            source,
9148            allow_structured_unresolved_owner,
9149            ignore_function_owner,
9150        );
9151    };
9152    let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
9153    let key = (
9154        anchor_start,
9155        anchor_end,
9156        allow_structured_unresolved_owner,
9157        ignore_function_owner,
9158    );
9159    if let Some(cached) = cache.borrow().get(&key).cloned() {
9160        return cached;
9161    }
9162    let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
9163        node,
9164        analyzer,
9165        visibility,
9166        file,
9167        source,
9168        allow_structured_unresolved_owner,
9169        ignore_function_owner,
9170    );
9171    cache.borrow_mut().insert(key, resolved.clone());
9172    resolved
9173}
9174
9175fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
9176    let mut current = node;
9177    loop {
9178        if matches!(
9179            current.kind(),
9180            "function_definition"
9181                | "class_specifier"
9182                | "struct_specifier"
9183                | "union_specifier"
9184                | "namespace_definition"
9185                | "translation_unit"
9186        ) {
9187            return (current.start_byte(), current.end_byte());
9188        }
9189        let Some(parent) = current.parent() else {
9190            return (current.start_byte(), current.end_byte());
9191        };
9192        current = parent;
9193    }
9194}
9195
9196fn enclosing_lexical_scope_components_with_unresolved_owner(
9197    node: Node<'_>,
9198    analyzer: &CppGraphSource<'_>,
9199    visibility: &VisibilityIndex<'_>,
9200    file: &ProjectFile,
9201    source: &str,
9202    allow_structured_unresolved_owner: bool,
9203    ignore_function_owner: bool,
9204) -> LexicalScopeResolution {
9205    #[cfg(any(test, feature = "test-support"))]
9206    LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
9207    // One ancestor climb collects the namespace chain, the class chain, the
9208    // nearest function definition and both displaced-class-shape facts.
9209    // `Node::parent` re-descends from the root on every call (tree-sitter
9210    // 0.24+), so the four separate climbs this replaces each cost another
9211    // near-full-AST scan per reconstruction on a large flat file (#1927).
9212    let mut namespaces = Vec::new();
9213    let mut classes = Vec::new();
9214    let mut function_definition = None;
9215    let mut displaced_class_scope = false;
9216    let mut current = node.parent();
9217    while let Some(parent) = current {
9218        match parent.kind() {
9219            "namespace_definition" => {
9220                if let Some(name) = parent.child_by_field_name("name") {
9221                    let mut components = Vec::new();
9222                    if append_cpp_name_components(name, source, &mut components).is_some() {
9223                        namespaces.push(components);
9224                    }
9225                }
9226            }
9227            "class_specifier" | "struct_specifier" | "union_specifier" => {
9228                if let Some(name) = parent.child_by_field_name("name") {
9229                    let mut components = Vec::new();
9230                    if append_cpp_name_components(name, source, &mut components).is_some() {
9231                        classes.push(components);
9232                    }
9233                }
9234            }
9235            "function_definition" => {
9236                if function_definition.is_none() {
9237                    function_definition = Some(parent);
9238                }
9239                displaced_class_scope = displaced_class_scope
9240                    || parent.child_by_field_name("type").is_some_and(|type_node| {
9241                        matches!(
9242                            type_node.kind(),
9243                            "class_specifier" | "struct_specifier" | "union_specifier"
9244                        )
9245                    })
9246                    || is_malformed_wrapper_function_definition(parent);
9247            }
9248            _ => {}
9249        }
9250        current = parent.parent();
9251    }
9252    namespaces.reverse();
9253    let namespace: Vec<String> = namespaces.into_iter().flatten().collect();
9254    let mut scope = namespace.clone();
9255    // A malformed namespace-sentinel wrapper can parse `namespace a::b` as a
9256    // qualified function declarator. It is recovery scaffolding, not a real
9257    // callable owner, and must not overwrite the indexed class scope retained
9258    // by the wrapper body (#2249).
9259    let has_qualified_function_owner = function_definition
9260        .filter(|function| !is_malformed_wrapper_function_definition(*function))
9261        .and_then(function_definition_owner_lookup_node)
9262        .is_some_and(|owner| {
9263            is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
9264        });
9265    let indexed_scope = displaced_class_scope
9266        .then(|| {
9267            indexed_structural_class_scope(visibility, file, node, source)
9268                .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9269        })
9270        .flatten()
9271        .or_else(|| {
9272            // A qualified out-of-line definition can lose its class owner from
9273            // the parser tree when a namespace sentinel or export macro wraps
9274            // the declaration.  Recover the indexed owner scope up front so
9275            // all unqualified type references in the body see the same class
9276            // boundary as C++ lookup, including aliases in parameters and
9277            // local declarations (not only template-argument leaves).
9278            (has_qualified_function_owner && function_definition.is_some())
9279                .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
9280                .flatten()
9281                .filter(|indexed| {
9282                    qualified_owner_scope_is_recoverable(
9283                        indexed,
9284                        &namespace,
9285                        &classes,
9286                        function_definition
9287                            .and_then(function_definition_owner_lookup_node)
9288                            .and_then(|owner| qualified_callable_owner_components(owner, source))
9289                            .map(|(components, _)| components),
9290                    )
9291                })
9292        })
9293        .or_else(|| {
9294            // A nested class declaration can likewise lose one of its outer
9295            // class ancestors from the CST.  Prefer the exact indexed
9296            // structural class scope. Its same-file declaration range and
9297            // exact template-id match already prove the owner, including a
9298            // specialization whose parser component retains only the primary
9299            // name. Keep the suffix guard for graph-only recovery, which lacks
9300            // that direct syntax-range proof.
9301            indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
9302                indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
9303                    qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
9304                })
9305            })
9306        })
9307        .or_else(|| {
9308            // Retain the existing indexed lexical-scope recovery for
9309            // unqualified function bodies.  It is intentionally last so a
9310            // canonical class owner wins whenever one is available.
9311            (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
9312                .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
9313                .flatten()
9314                .filter(|indexed| indexed.len() > namespace.len())
9315        });
9316    if let Some(indexed_scope) = indexed_scope.as_ref() {
9317        // A macro-displaced namespace can leave the parser with the real class
9318        // body but no namespace ancestor. Prefer the structural class match;
9319        // partial specializations whose structured name cannot round-trip use
9320        // the exact indexed enclosing-owner chain instead.
9321        scope = indexed_scope.clone();
9322        classes.clear();
9323    }
9324
9325    if !ignore_function_owner
9326        && has_qualified_function_owner
9327        && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
9328    {
9329        let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
9330            return LexicalScopeResolution::Missing;
9331        };
9332        // Resolve the out-of-line owner from the parser namespace before the
9333        // provisional indexed parent can influence the answer. Per-file
9334        // extraction can assign the first same-depth using namespace to a
9335        // bare owner. The structured using resolver instead selects the
9336        // namespace whose visible class has the owner name (#1838).
9337        let imports = visibility.ordinary_type_import_cell(file);
9338        let owner_resolution = resolve_type_components_lexically_at_scoped(
9339            function,
9340            &owner,
9341            global,
9342            analyzer,
9343            visibility,
9344            &imports,
9345            file,
9346            source,
9347            None,
9348            false,
9349            false,
9350            false,
9351            namespace.clone(),
9352        );
9353        match owner_resolution {
9354            LexicalTypeResolution::Resolved {
9355                unit, components, ..
9356            } if is_indexed_class_owner(analyzer, &unit) => {
9357                scope = components;
9358                classes.clear();
9359            }
9360            LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
9361            LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
9362                match visibility
9363                    .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
9364                {
9365                    LexicalTypeResolution::Resolved { components, .. } => scope = components,
9366                    LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
9367                    LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
9368                        if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
9369                            qualified_owner_scope_is_recoverable(
9370                                indexed,
9371                                &namespace,
9372                                &classes,
9373                                Some(owner.clone()),
9374                            )
9375                        }) {
9376                            scope = indexed.clone();
9377                        } else {
9378                            scope = if global || owner.starts_with(&namespace) {
9379                                owner
9380                            } else {
9381                                let mut relative = namespace;
9382                                relative.extend(owner);
9383                                relative
9384                            };
9385                        }
9386                    }
9387                    LexicalTypeResolution::Missing => {
9388                        // Structural lexical resolution cannot see an owner class that
9389                        // is reachable only through an in-scope `using namespace`
9390                        // directive, so it would otherwise hard-fail here. The indexed
9391                        // definition already carries the true fully-qualified owner
9392                        // (its package reflects the directive), so recover the real
9393                        // enclosing scope from the analyzer graph -- exactly the scope
9394                        // chain real C++ unqualified lookup traverses. Only the strict
9395                        // callers reach this arm; the best-effort callers above keep
9396                        // their existing structural guess (and its query profile).
9397                        match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
9398                            Some(indexed) => scope = indexed,
9399                            None => return LexicalScopeResolution::Missing,
9400                        }
9401                    }
9402                }
9403            }
9404        }
9405    }
9406
9407    classes.reverse();
9408    scope.extend(classes.into_iter().flatten());
9409    LexicalScopeResolution::Resolved(scope)
9410}
9411
9412fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
9413    let mut current = node.parent();
9414    while let Some(parent) = current {
9415        if parent.kind() == "function_definition"
9416            && parent.child_by_field_name("type").is_some_and(|type_node| {
9417                matches!(
9418                    type_node.kind(),
9419                    "class_specifier" | "struct_specifier" | "union_specifier"
9420                )
9421            })
9422        {
9423            return true;
9424        }
9425        current = parent.parent();
9426    }
9427    false
9428}
9429
9430fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
9431    let mut current = node.parent();
9432    while let Some(parent) = current {
9433        if parent.kind() == "function_definition"
9434            && is_malformed_wrapper_function_definition(parent)
9435        {
9436            return true;
9437        }
9438        current = parent.parent();
9439    }
9440    false
9441}
9442
9443fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
9444    node.has_error()
9445        && node
9446            .child_by_field_name("declarator")
9447            .is_some_and(|declarator| {
9448                declarator.kind() != "function_declarator"
9449                    && first_descendant_of_kind(declarator, "function_declarator").is_none()
9450            })
9451}
9452
9453/// Tree-sitter can make an attribute/nullability macro look like the namespace
9454/// component of a qualified function owner when it appears between the return
9455/// type and the declarator (for example `CordRep* absl_nullable VerifyTree`).
9456/// The recovered owner is not a C++ lexical owner, so callers resolving the
9457/// ordinary return/parameter type must retain the surrounding namespace scope.
9458fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
9459    node.child_by_field_name("scope")
9460        .and_then(|scope| recovered_macro_decorated_type_node(scope))
9461        .is_some()
9462}
9463
9464fn indexed_structural_class_scope(
9465    visibility: &VisibilityIndex<'_>,
9466    file: &ProjectFile,
9467    node: Node<'_>,
9468    source: &str,
9469) -> Option<Vec<String>> {
9470    let mut current = node.parent();
9471    while let Some(parent) = current {
9472        if matches!(
9473            parent.kind(),
9474            "class_specifier" | "struct_specifier" | "union_specifier"
9475        ) {
9476            return visibility.indexed_structural_class_scope(file, parent, source);
9477        }
9478        current = parent.parent();
9479    }
9480    None
9481}
9482
9483/// Check that an indexed owner scope is a structured completion of the parser
9484/// scope rather than an unrelated same-spelled declaration.
9485///
9486/// Error recovery around C++ namespace sentinels can preserve only a subset of
9487/// the namespace/class chain.  The indexed definition still carries the full
9488/// owner path, so require every surviving parser component to occur in order
9489/// and require any explicit qualified function owner to be the terminal
9490/// suffix.  An empty parser scope is accepted only with that qualified-owner
9491/// suffix evidence; a lone top-level short name is not evidence that a
9492/// namespace was lost.
9493fn qualified_owner_scope_is_recoverable(
9494    indexed: &[String],
9495    namespace: &[String],
9496    classes: &[Vec<String>],
9497    qualified_owner: Option<Vec<String>>,
9498) -> bool {
9499    if let Some(owner) = qualified_owner {
9500        if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
9501            return false;
9502        }
9503        // A malformed namespace sentinel can erase every parser namespace
9504        // ancestor.  The indexed enclosing callable still provides an
9505        // authoritative class owner, so the qualified owner suffix itself is
9506        // enough evidence in that case.  When namespace components survived,
9507        // retain the stricter subsequence check below.
9508        if namespace.is_empty() {
9509            return true;
9510        }
9511        if indexed.len() <= namespace.len() {
9512            return false;
9513        }
9514        let mut prefix = indexed.iter();
9515        return namespace
9516            .iter()
9517            .all(|component| prefix.any(|candidate| candidate == component));
9518    }
9519    let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
9520    if !class_components.is_empty() {
9521        return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
9522    }
9523    if namespace.is_empty() || indexed.len() <= namespace.len() {
9524        return false;
9525    }
9526    let mut prefix = indexed.iter();
9527    namespace
9528        .iter()
9529        .all(|component| prefix.any(|candidate| candidate == component))
9530}
9531
9532/// Whether `unit` is a real (non-alias) class owner. A `using` alias never
9533/// counts as the true lexical owner recovered from the indexed graph.
9534fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
9535    unit.is_class()
9536        && !analyzer
9537            .type_alias_provider()
9538            .is_some_and(|provider| provider.is_type_alias(unit))
9539}
9540
9541/// Recover the enclosing member's true lexical scope from the *indexed*
9542/// definition when structural resolution cannot see the owner class.
9543///
9544/// An out-of-line member defined at file scope (`int HTMLLayout::method()
9545/// {...}`) whose owner class is reachable only through an in-scope `using
9546/// namespace X;` directive cannot be resolved by `resolve_type_components_
9547/// lexically`, which walks structural lexical tiers and never consults
9548/// using-directives. The definition itself, however, is indexed with its true
9549/// fully-qualified identity (its package already reflects the directive), so
9550/// the analyzer graph knows the real owner. Walk from the reference's indexed
9551/// enclosing code unit up to the innermost enclosing class and return that
9552/// class's fully-qualified scope components (e.g. `["log4cxx", "HTMLLayout"]`)
9553/// -- exactly the scope chain C++ unqualified lookup traverses.
9554fn indexed_enclosing_owner_scope(
9555    analyzer: &CppGraphSource<'_>,
9556    visibility: &VisibilityIndex<'_>,
9557    file: &ProjectFile,
9558    node: Node<'_>,
9559) -> Option<Vec<String>> {
9560    visibility.indexed_enclosing_owner_scope(analyzer, file, node)
9561}
9562
9563fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9564    let start = enclosing_context(node, ctx).enclosing?;
9565    brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
9566        ctx.analyzer.parent_of(unit)
9567    })
9568    .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
9569}
9570
9571pub fn resolve_type_node_lexically(
9572    node: Node<'_>,
9573    analyzer: &CppGraphSource<'_>,
9574    visibility: &VisibilityIndex<'_>,
9575    ordinary_type_imports: &OrdinaryTypeImportCell,
9576    file: &ProjectFile,
9577    source: &str,
9578) -> LexicalTypeResolution {
9579    let Some((components, global)) = type_reference_components(node, source) else {
9580        return LexicalTypeResolution::Missing;
9581    };
9582    let resolution = resolve_type_components_lexically_at(
9583        node,
9584        &components,
9585        global,
9586        analyzer,
9587        visibility,
9588        ordinary_type_imports,
9589        file,
9590        source,
9591    );
9592    if !is_cpp_template_argument_type_leaf(node) {
9593        return resolution;
9594    }
9595
9596    // Error recovery can detach a member function from its class while
9597    // leaving an unqualified type argument (for example `error_type` in
9598    // `expected<..., error_type>`). The normal structural scope then lacks
9599    // the class owner and resolves the wrong same-spelled alias, or fails
9600    // closed. The indexed enclosing unit still carries the authoritative
9601    // class scope; retry only this narrowly-shaped leaf with that scope.
9602    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
9603        return resolution;
9604    };
9605    let namespace_scope = enclosing_namespace_components(node, source);
9606    if indexed_scope.len() <= namespace_scope.len() {
9607        return resolution;
9608    }
9609    let indexed = visibility.resolve_type_components_lexically(
9610        analyzer,
9611        file,
9612        &components,
9613        global,
9614        &indexed_scope,
9615    );
9616    match indexed {
9617        LexicalTypeResolution::Resolved { ref unit, .. }
9618            if !visibility
9619                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
9620        {
9621            resolution
9622        }
9623        LexicalTypeResolution::Resolved { .. } => indexed,
9624        _ => resolution,
9625    }
9626}
9627
9628#[allow(clippy::too_many_arguments)]
9629pub fn resolve_type_node_lexically_for_target(
9630    node: Node<'_>,
9631    analyzer: &CppGraphSource<'_>,
9632    visibility: &VisibilityIndex<'_>,
9633    ordinary_type_imports: &OrdinaryTypeImportCell,
9634    file: &ProjectFile,
9635    source: &str,
9636    target: &CodeUnit,
9637    scope_cache: Option<&LexicalScopeCache>,
9638    recovered_scope: Option<&[String]>,
9639) -> LexicalTypeResolution {
9640    let Some((reference_components, global)) = type_reference_components(node, source) else {
9641        return LexicalTypeResolution::Missing;
9642    };
9643    let terminal = reference_components
9644        .last()
9645        .expect("type reference components are non-empty");
9646    if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
9647        return LexicalTypeResolution::Missing;
9648    }
9649    let template_arguments = cpp_template_reference_arguments(node, source);
9650    let selects_concrete_specialization =
9651        template_arguments.is_some() && visibility.is_template_specialization(target);
9652    if !selects_concrete_specialization
9653        && !visibility.structured_type_reference_may_resolve_to_target(
9654            analyzer,
9655            file,
9656            std::slice::from_ref(terminal),
9657            false,
9658            &[],
9659            target,
9660        )
9661    {
9662        return LexicalTypeResolution::Missing;
9663    }
9664    if let Some(arguments) = template_arguments.as_ref() {
9665        let alias_resolution = if let Some(recovered_scope) = recovered_scope {
9666            resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
9667                node,
9668                &reference_components,
9669                global,
9670                analyzer,
9671                visibility,
9672                ordinary_type_imports,
9673                file,
9674                source,
9675                recovered_scope,
9676            )
9677        } else {
9678            resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
9679                node,
9680                &reference_components,
9681                global,
9682                analyzer,
9683                visibility,
9684                ordinary_type_imports,
9685                file,
9686                source,
9687                scope_cache,
9688            )
9689        };
9690        return match alias_resolution {
9691            LexicalTypeResolution::Resolved {
9692                unit,
9693                components,
9694                candidates,
9695            } if visibility.template_alias_arguments_preserve_target(
9696                analyzer, file, &unit, arguments, target,
9697            ) =>
9698            {
9699                LexicalTypeResolution::Resolved {
9700                    unit: target.clone(),
9701                    components,
9702                    candidates,
9703                }
9704            }
9705            LexicalTypeResolution::Resolved {
9706                unit,
9707                components,
9708                candidates,
9709            } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
9710                Ok(resolved_unit) => {
9711                    let target_guided = (!same_visible_symbol(&resolved_unit, target))
9712                        .then(|| {
9713                            target_guided_malformed_template_alias_resolution(
9714                                node,
9715                                analyzer,
9716                                visibility,
9717                                file,
9718                                arguments,
9719                                &reference_components,
9720                                target,
9721                            )
9722                        })
9723                        .flatten();
9724                    target_guided.unwrap_or(LexicalTypeResolution::Resolved {
9725                        unit: resolved_unit,
9726                        components,
9727                        candidates,
9728                    })
9729                }
9730                Err(_) => LexicalTypeResolution::Ambiguous,
9731            },
9732            LexicalTypeResolution::Missing => {
9733                let target_preserving = if let Some(recovered_scope) = recovered_scope {
9734                    resolve_type_components_lexically_at_for_target_with_recovered_scope(
9735                        node,
9736                        &reference_components,
9737                        global,
9738                        analyzer,
9739                        visibility,
9740                        ordinary_type_imports,
9741                        file,
9742                        source,
9743                        target,
9744                        true,
9745                        recovered_scope,
9746                    )
9747                } else {
9748                    resolve_type_components_lexically_at_for_target_with_scope_cache(
9749                        node,
9750                        &reference_components,
9751                        global,
9752                        analyzer,
9753                        visibility,
9754                        ordinary_type_imports,
9755                        file,
9756                        source,
9757                        target,
9758                        true,
9759                        scope_cache,
9760                    )
9761                };
9762                match target_preserving {
9763                    LexicalTypeResolution::Resolved {
9764                        unit: _,
9765                        components,
9766                        candidates,
9767                    } if template_reference_candidates_select_target(
9768                        node,
9769                        &candidates,
9770                        analyzer,
9771                        visibility,
9772                        file,
9773                        source,
9774                        target,
9775                    ) =>
9776                    {
9777                        LexicalTypeResolution::Resolved {
9778                            unit: target.clone(),
9779                            components,
9780                            candidates,
9781                        }
9782                    }
9783                    _ => target_guided_malformed_template_alias_resolution(
9784                        node,
9785                        analyzer,
9786                        visibility,
9787                        file,
9788                        arguments,
9789                        &reference_components,
9790                        target,
9791                    )
9792                    .unwrap_or(LexicalTypeResolution::Missing),
9793                }
9794            }
9795            LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
9796        };
9797    }
9798    let resolution = if let Some(recovered_scope) = recovered_scope {
9799        resolve_type_components_lexically_at_for_target_with_recovered_scope(
9800            node,
9801            &reference_components,
9802            global,
9803            analyzer,
9804            visibility,
9805            ordinary_type_imports,
9806            file,
9807            source,
9808            target,
9809            true,
9810            recovered_scope,
9811        )
9812    } else {
9813        resolve_type_components_lexically_at_for_target_with_scope_cache(
9814            node,
9815            &reference_components,
9816            global,
9817            analyzer,
9818            visibility,
9819            ordinary_type_imports,
9820            file,
9821            source,
9822            target,
9823            true,
9824            scope_cache,
9825        )
9826    };
9827    let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
9828        target_guided_qualified_namespace_function_type_resolution(
9829            node,
9830            &reference_components,
9831            global,
9832            analyzer,
9833            visibility,
9834            ordinary_type_imports,
9835            file,
9836            source,
9837            target,
9838        )
9839        .unwrap_or(resolution)
9840    } else {
9841        resolution
9842    };
9843    if !is_cpp_template_argument_type_leaf(node) {
9844        return resolution;
9845    }
9846
9847    // Preprocessor recovery can lift a member declaration out of its class
9848    // field list.  The unqualified template argument is then resolved from
9849    // the namespace only, even though the indexed enclosing callable still
9850    // identifies the class owner.  Retry this exact leaf against that
9851    // structured owner scope; ordinary type nodes must continue to use the
9852    // parser-derived lexical scope so unrelated same-spelled aliases remain
9853    // excluded.
9854    let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
9855        return resolution;
9856    };
9857    let namespace_scope = enclosing_namespace_components(node, source);
9858    if indexed_scope.len() <= namespace_scope.len() {
9859        return resolution;
9860    }
9861    let indexed = visibility.resolve_type_components_lexically_for_target(
9862        analyzer,
9863        file,
9864        &reference_components,
9865        global,
9866        &indexed_scope,
9867        target,
9868    );
9869    match indexed {
9870        LexicalTypeResolution::Resolved {
9871            ref unit,
9872            ref candidates,
9873            ..
9874        } if (same_visible_symbol(unit, target)
9875            || candidates
9876                .iter()
9877                .any(|candidate| same_visible_symbol(candidate, target)))
9878            && visibility
9879                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
9880        {
9881            indexed
9882        }
9883        _ => resolution,
9884    }
9885}
9886
9887/// Recover the lexical namespace of an out-of-line namespace function whose
9888/// relative qualifier is reachable through a `using namespace` directive.
9889///
9890/// Per-file declaration extraction can attach `void schema::consume(...)` to
9891/// the first visible namespace prefix when more than one using-directive is
9892/// active. A matching visible free-function declaration still proves the
9893/// complete namespace. Target-guided inverse lookup may use that namespace to
9894/// retry a parameter or body type, but only when the target lives in the same
9895/// namespace and the declaration's callable arity matches the definition.
9896#[allow(clippy::too_many_arguments)]
9897fn target_guided_qualified_namespace_function_type_resolution(
9898    node: Node<'_>,
9899    components: &[String],
9900    global: bool,
9901    analyzer: &CppGraphSource<'_>,
9902    visibility: &VisibilityIndex<'_>,
9903    ordinary_type_imports: &OrdinaryTypeImportCell,
9904    file: &ProjectFile,
9905    source: &str,
9906    target: &CodeUnit,
9907) -> Option<LexicalTypeResolution> {
9908    let function_definition = std::iter::successors(Some(node), |current| current.parent())
9909        .find(|current| current.kind() == "function_definition")?;
9910    let function = function_definition_name_node(function_definition)?;
9911    let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
9912    let target_namespace = target.package_name();
9913    if target_namespace.is_empty() {
9914        return None;
9915    }
9916    let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9917        brokk_bifrost_core::analyzer::Language::Cpp,
9918        target_namespace,
9919    );
9920    if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
9921    {
9922        return None;
9923    }
9924
9925    let function_name = node_text(function_terminal_node(function), source);
9926    let definition_arity = signature_arity(Some(node_text(function_definition, source)));
9927    let declaration_proves_namespace = visibility
9928        .visible_identifier_candidates(file, function_name)
9929        .filter(|candidate| {
9930            candidate.is_function()
9931                && type_owner_of(analyzer, candidate).is_none()
9932                && candidate.package_name() == target_namespace
9933        })
9934        .any(|candidate| cpp_callable_arity(analyzer, candidate).accepts(definition_arity));
9935    if !declaration_proves_namespace {
9936        return None;
9937    }
9938
9939    let resolution = resolve_type_components_lexically_at_scoped(
9940        node,
9941        components,
9942        global,
9943        analyzer,
9944        visibility,
9945        ordinary_type_imports,
9946        file,
9947        source,
9948        Some(target),
9949        true,
9950        false,
9951        false,
9952        target_scope,
9953    );
9954    match &resolution {
9955        LexicalTypeResolution::Resolved {
9956            unit, candidates, ..
9957        } if (same_visible_symbol(unit, target)
9958            || candidates
9959                .iter()
9960                .any(|candidate| same_visible_symbol(candidate, target)))
9961            && visibility
9962                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
9963        {
9964            Some(resolution)
9965        }
9966        LexicalTypeResolution::Resolved { .. }
9967        | LexicalTypeResolution::Ambiguous
9968        | LexicalTypeResolution::Missing => None,
9969    }
9970}
9971
9972#[allow(clippy::too_many_arguments)]
9973fn target_guided_malformed_template_alias_resolution(
9974    node: Node<'_>,
9975    analyzer: &CppGraphSource<'_>,
9976    visibility: &VisibilityIndex<'_>,
9977    file: &ProjectFile,
9978    arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
9979    components: &[String],
9980    target: &CodeUnit,
9981) -> Option<LexicalTypeResolution> {
9982    if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
9983        return None;
9984    }
9985
9986    let identifier = &components[0];
9987    let namespace =
9988        visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
9989    let namespace_name = namespace.join("::");
9990    let candidates = visibility
9991        .visible_identifier_candidates(file, identifier)
9992        .filter(|candidate| {
9993            cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
9994                && visibility.type_candidate_may_be_visible_before_reference(
9995                    analyzer,
9996                    file,
9997                    candidate,
9998                    node.start_byte(),
9999                )
10000        })
10001        .cloned()
10002        .collect::<Vec<_>>();
10003    let first = candidates.first()?;
10004    if !candidates
10005        .iter()
10006        .all(|candidate| same_logical_symbol(first, candidate))
10007        || !candidates.iter().all(|candidate| {
10008            visibility.template_alias_arguments_preserve_target(
10009                analyzer, file, candidate, arguments, target,
10010            )
10011        })
10012    {
10013        return None;
10014    }
10015
10016    let mut resolved_components = namespace;
10017    resolved_components.push(identifier.clone());
10018    Some(LexicalTypeResolution::Resolved {
10019        unit: target.clone(),
10020        components: resolved_components,
10021        candidates,
10022    })
10023}
10024
10025fn resolve_type_node_lexically_for_target_without_visibility(
10026    node: Node<'_>,
10027    analyzer: &CppGraphSource<'_>,
10028    visibility: &VisibilityIndex<'_>,
10029    file: &ProjectFile,
10030    source: &str,
10031    target: &CodeUnit,
10032) -> LexicalTypeResolution {
10033    let Some((components, global)) = type_reference_components(node, source) else {
10034        return LexicalTypeResolution::Missing;
10035    };
10036    let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10037        node,
10038        analyzer,
10039        visibility,
10040        file,
10041        source,
10042        true,
10043        recovered_macro_decorated_declarator_type(node)
10044            == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10045    ) {
10046        LexicalScopeResolution::Resolved(scope) => scope,
10047        LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10048        LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10049    };
10050    visibility.resolve_type_components_lexically_for_target(
10051        analyzer,
10052        file,
10053        &components,
10054        global,
10055        &lexical_scope,
10056        target,
10057    )
10058}
10059
10060fn type_node_has_exact_target_identity_without_visibility(
10061    node: Node<'_>,
10062    analyzer: &CppGraphSource<'_>,
10063    visibility: &VisibilityIndex<'_>,
10064    file: &ProjectFile,
10065    source: &str,
10066    target: &CodeUnit,
10067) -> bool {
10068    let Some((components, global)) = type_reference_components(node, source) else {
10069        return false;
10070    };
10071    let LexicalScopeResolution::Resolved(lexical_scope) =
10072        enclosing_lexical_scope_components_with_unresolved_owner(
10073            node,
10074            analyzer,
10075            visibility,
10076            file,
10077            source,
10078            true,
10079            recovered_macro_decorated_declarator_type(node)
10080                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10081        )
10082    else {
10083        return false;
10084    };
10085    let target_name = cpp_name_for(target);
10086    lexical_component_tiers(&components, global, &lexical_scope)
10087        .any(|qualified| qualified.join("::") == target_name)
10088}
10089
10090pub fn resolve_using_enum_declaration_owner(
10091    node: Node<'_>,
10092    analyzer: &CppGraphSource<'_>,
10093    visibility: &VisibilityIndex<'_>,
10094    ordinary_type_imports: &OrdinaryTypeImportCell,
10095    file: &ProjectFile,
10096    source: &str,
10097) -> LexicalTypeResolution {
10098    let Some(type_node) = using_enum_declaration_type_node(node) else {
10099        return LexicalTypeResolution::Missing;
10100    };
10101    let mut components = Vec::new();
10102    if append_cpp_name_components(type_node, source, &mut components).is_none()
10103        || components.is_empty()
10104    {
10105        return LexicalTypeResolution::Missing;
10106    }
10107    resolve_type_components_lexically_at(
10108        type_node,
10109        &components,
10110        is_globally_qualified_cpp_name(type_node),
10111        analyzer,
10112        visibility,
10113        ordinary_type_imports,
10114        file,
10115        source,
10116    )
10117}
10118
10119pub fn resolve_ordinary_using_declaration_owner(
10120    node: Node<'_>,
10121    analyzer: &CppGraphSource<'_>,
10122    visibility: &VisibilityIndex<'_>,
10123    file: &ProjectFile,
10124    source: &str,
10125) -> LexicalTypeResolution {
10126    let Some(type_node) = ordinary_using_declaration_type_node(node) else {
10127        return LexicalTypeResolution::Missing;
10128    };
10129    let mut components = Vec::new();
10130    if append_cpp_name_components(type_node, source, &mut components).is_none()
10131        || components.len() < 2
10132    {
10133        return LexicalTypeResolution::Missing;
10134    }
10135    let lexical_scope =
10136        match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
10137            LexicalScopeResolution::Resolved(scope) => scope,
10138            LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10139            LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10140        };
10141    visibility.resolve_type_components_lexically(
10142        analyzer,
10143        file,
10144        &components,
10145        is_globally_qualified_cpp_name(type_node),
10146        &lexical_scope,
10147    )
10148}
10149
10150pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10151    (node.kind() == "using_declaration"
10152        && (0..node.child_count()).any(|index| {
10153            node.child(index)
10154                .is_some_and(|child| child.kind() == "enum")
10155        }))
10156    .then(|| node.named_child(0))
10157    .flatten()
10158}
10159
10160pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
10161    (node.kind() == "using_declaration"
10162        && using_enum_declaration_type_node(node).is_none()
10163        && using_namespace_directive_name_node(node).is_none())
10164    .then(|| node.named_child(0))
10165    .flatten()
10166}
10167
10168/// Tree-sitter can recover `using ::absl::cord_internal::CordRep;` after an
10169/// undefined namespace-sentinel macro as a declaration whose type is the
10170/// all-caps sentinel and whose qualified declarator starts with a pseudo
10171/// `using` scope. The real imported name remains a structured qualified
10172/// identifier under that declarator. Recover only this exact CST envelope so
10173/// ordinary macro-decorated variables are not treated as imports.
10174fn recovered_macro_using_declaration_type_node<'tree>(
10175    node: Node<'tree>,
10176    source: &str,
10177) -> Option<(Node<'tree>, bool)> {
10178    if node.kind() != "declaration" {
10179        return None;
10180    }
10181    let macro_type = node.child_by_field_name("type")?;
10182    if macro_type.kind() != "type_identifier"
10183        || !cpp_export_macro_token(node_text(macro_type, source))
10184    {
10185        return None;
10186    }
10187    let declarator = node.child_by_field_name("declarator")?;
10188    if declarator.kind() != "qualified_identifier" {
10189        return None;
10190    }
10191    let scope = declarator.child_by_field_name("scope")?;
10192    if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
10193        return None;
10194    }
10195    let target = declarator.child_by_field_name("name")?;
10196    let mut components = Vec::new();
10197    append_cpp_name_components(target, source, &mut components)?;
10198    (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
10199}
10200
10201fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
10202    let is_directive = node.kind() == "using_directive"
10203        || (node.kind() == "using_declaration"
10204            && (0..node.child_count()).any(|index| {
10205                node.child(index)
10206                    .is_some_and(|child| child.kind() == "namespace")
10207            }));
10208    if !is_directive {
10209        return None;
10210    }
10211    node.child_by_field_name("name")
10212        .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
10213}
10214
10215fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10216    let mut current = node.parent();
10217    while let Some(parent) = current {
10218        if matches!(
10219            parent.kind(),
10220            "compound_statement"
10221                | "function_definition"
10222                | "lambda_expression"
10223                | "for_statement"
10224                | "while_statement"
10225                | "if_statement"
10226                | "class_specifier"
10227                | "struct_specifier"
10228                | "union_specifier"
10229        ) {
10230            return None;
10231        }
10232        current = parent.parent();
10233    }
10234    Some(enclosing_namespace_components(node, source))
10235}
10236
10237fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
10238    let mut current = node.parent();
10239    while let Some(scope) = current {
10240        if matches!(
10241            scope.kind(),
10242            "compound_statement"
10243                | "declaration_list"
10244                | "field_declaration_list"
10245                | "translation_unit"
10246        ) {
10247            let mut depth = 0;
10248            let mut ancestor = scope.parent();
10249            while let Some(parent) = ancestor {
10250                depth += 1;
10251                ancestor = parent.parent();
10252            }
10253            return Some((
10254                scope.start_byte(),
10255                scope.end_byte(),
10256                depth,
10257                scope.kind() == "compound_statement",
10258            ));
10259        }
10260        current = scope.parent();
10261    }
10262    None
10263}
10264
10265/// Build the per-file structured using index for `file`.
10266///
10267/// The result is a pure function of the file's parsed content, which is what
10268/// lets `CppSource::source_using_index` memoize it on the analyzer (#1927):
10269/// a `VisibilityIndex` is rebuilt per usage query, and rebuilding this index
10270/// per query re-walked a 9.5 MB amalgamation's AST for every candidate.
10271pub fn build_source_using_index(
10272    cpp: &dyn CppSource,
10273    token: QueryToken<'_>,
10274    file: &ProjectFile,
10275) -> SourceUsingIndex {
10276    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10277        return SourceUsingIndex::default();
10278    };
10279    collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
10280}
10281
10282fn collect_source_using_index(
10283    cpp: &dyn CppSource,
10284    source_file: &ProjectFile,
10285    root: Node<'_>,
10286    source: &str,
10287) -> SourceUsingIndex {
10288    #[cfg(not(any(test, feature = "test-support")))]
10289    let _ = cpp;
10290    let mut index = SourceUsingIndex::default();
10291    let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
10292    let mut stack = vec![root];
10293    while let Some(node) = stack.pop() {
10294        let target = match node.kind() {
10295            "using_directive" | "using_declaration" => {
10296                if let Some(namespace_node) = using_namespace_directive_name_node(node) {
10297                    let mut namespace_components = Vec::new();
10298                    append_cpp_name_components(namespace_node, source, &mut namespace_components)
10299                        .map(|_| EffectiveUsingTarget::Namespace {
10300                            namespace_components,
10301                            global: is_globally_qualified_cpp_name(namespace_node),
10302                        })
10303                } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
10304                    let mut target_components = Vec::new();
10305                    (append_cpp_name_components(type_node, source, &mut target_components)
10306                        .is_some()
10307                        && target_components.len() >= 2)
10308                        .then(|| EffectiveUsingTarget::Ordinary {
10309                            name: target_components
10310                                .last()
10311                                .expect("ordinary using has a terminal component")
10312                                .clone(),
10313                            target_components,
10314                            global: is_globally_qualified_cpp_name(type_node),
10315                        })
10316                } else {
10317                    None
10318                }
10319            }
10320            "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
10321                |(type_node, global)| {
10322                    let mut target_components = Vec::new();
10323                    (append_cpp_name_components(type_node, source, &mut target_components)
10324                        .is_some()
10325                        && target_components.len() >= 2)
10326                        .then(|| EffectiveUsingTarget::Ordinary {
10327                            name: target_components
10328                                .last()
10329                                .expect("recovered ordinary using has a terminal component")
10330                                .clone(),
10331                            target_components,
10332                            global,
10333                        })
10334                },
10335            ),
10336            _ => None,
10337        };
10338        if let Some(target) = target {
10339            // Guard ancestry is one of the most expensive tree-sitter operations: Node::parent
10340            // searches from the root. The project index visits every AST node, but only these
10341            // structured using declarations need a guard environment. Keep the cheap target
10342            // classification ahead of both ancestor walks so non-using nodes remain a one-pass
10343            // walk and the total cost is O(nodes + using declarations * ancestor depth).
10344            #[cfg(any(test, feature = "test-support"))]
10345            cpp.record_using_guard_context_inspection_for_test();
10346            let required_guards = if callable_preprocessor_context_is_visible(node, source) {
10347                Some(HashSet::default())
10348            } else {
10349                preprocessor_guard_environment(node, source)
10350            };
10351            let Some(required_guards) = required_guards else {
10352                let mut cursor = node.walk();
10353                stack.extend(node.children(&mut cursor));
10354                continue;
10355            };
10356            if let Some((scope_start, scope_end, scope_depth, block_scope)) =
10357                ordinary_using_scope(node)
10358            {
10359                let declaration_namespace = enclosing_namespace_components(node, source);
10360                let declaration_namespace = if declaration_namespace.is_empty() {
10361                    recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
10362                        .unwrap_or(declaration_namespace)
10363                } else {
10364                    declaration_namespace
10365                };
10366                let namespace_scope = using_named_scope(node, source);
10367                let lexical_depth = declaration_namespace.len();
10368                let binding = OrdinaryTypeImport {
10369                    target,
10370                    source: source_file.clone(),
10371                    declaration_byte: node.end_byte(),
10372                    scope_start,
10373                    scope_end,
10374                    scope_depth,
10375                    block_scope,
10376                    lexical_depth,
10377                    declaration_namespace,
10378                    namespace_scope,
10379                    resolved_target_components: None,
10380                    required_guards,
10381                };
10382                match &binding.target {
10383                    EffectiveUsingTarget::Ordinary { name, .. } => index
10384                        .ordinary_by_name
10385                        .entry(name.clone())
10386                        .or_default()
10387                        .push(binding),
10388                    EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
10389                }
10390            }
10391        }
10392        let mut cursor = node.walk();
10393        stack.extend(node.children(&mut cursor));
10394    }
10395    index
10396}
10397
10398struct OrphanedNamespaceEnvelope {
10399    body_end: usize,
10400    components: Vec<String>,
10401    class_names: HashSet<String>,
10402}
10403
10404/// Tree-sitter can terminate a namespace body at an object-like namespace
10405/// macro (for example `ABSL_NAMESPACE_BEGIN`), then parse the following
10406/// out-of-line definitions at translation-unit scope. A block-scoped using
10407/// declaration in one of those definitions still belongs to the namespace
10408/// selected by the malformed namespace envelope. Keep the envelope scan
10409/// source-local and reuse its structural ownership evidence for each using.
10410fn collect_orphaned_namespace_envelopes(
10411    root: Node<'_>,
10412    source: &str,
10413) -> Vec<OrphanedNamespaceEnvelope> {
10414    let mut envelopes = Vec::new();
10415    let mut stack = vec![root];
10416    while let Some(current) = stack.pop() {
10417        if current.kind() == "namespace_definition"
10418            && let Some(body) = current.child_by_field_name("body")
10419            && current.end_byte() == body.end_byte()
10420            && let Some(name) = current.child_by_field_name("name")
10421        {
10422            let mut components = enclosing_namespace_components(current, source);
10423            if append_cpp_name_components(name, source, &mut components).is_some()
10424                && !components.is_empty()
10425            {
10426                let mut class_names = HashSet::default();
10427                let mut body_stack = vec![body];
10428                while let Some(node) = body_stack.pop() {
10429                    if let Some(name) = orphaned_class_definition_name(node, source) {
10430                        class_names.insert(name);
10431                    }
10432                    let mut cursor = node.walk();
10433                    if node.kind() == "ERROR" {
10434                        body_stack.extend(node.children(&mut cursor));
10435                    } else {
10436                        body_stack.extend(node.named_children(&mut cursor));
10437                    }
10438                }
10439                envelopes.push(OrphanedNamespaceEnvelope {
10440                    body_end: body.end_byte(),
10441                    components,
10442                    class_names,
10443                });
10444            }
10445        }
10446        let mut cursor = current.walk();
10447        stack.extend(current.named_children(&mut cursor));
10448    }
10449    envelopes
10450}
10451
10452/// Lightweight type-reference variant of [`collect_orphaned_namespace_envelopes`].
10453/// Alias and qualified-owner recovery predate the bounded class-type scope and
10454/// retain their target-specific ambiguity checks, so they need only the
10455/// error-marked namespace prefix and its truncated body boundary.
10456fn collect_orphaned_namespace_type_envelopes(
10457    root: Node<'_>,
10458    source: &str,
10459) -> Vec<OrphanedNamespaceEnvelope> {
10460    let mut envelopes = Vec::new();
10461    let mut stack = vec![root];
10462    while let Some(current) = stack.pop() {
10463        if current.kind() == "namespace_definition"
10464            && current.has_error()
10465            && let Some(body) = current.child_by_field_name("body")
10466            && current.end_byte() == body.end_byte()
10467            && let Some(name) = current.child_by_field_name("name")
10468        {
10469            let mut components = enclosing_namespace_components(current, source);
10470            if append_cpp_name_components(name, source, &mut components).is_some() {
10471                envelopes.push(OrphanedNamespaceEnvelope {
10472                    body_end: body.end_byte(),
10473                    components,
10474                    class_names: HashSet::default(),
10475                });
10476            }
10477        }
10478        if !current.has_error() {
10479            continue;
10480        }
10481        let mut cursor = current.walk();
10482        stack.extend(
10483            current
10484                .named_children(&mut cursor)
10485                .filter(|child| child.has_error()),
10486        );
10487    }
10488    envelopes
10489}
10490
10491fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
10492    if matches!(
10493        node.kind(),
10494        "class_specifier" | "struct_specifier" | "union_specifier"
10495    ) {
10496        let body = node.child_by_field_name("body")?;
10497        let name = node.child_by_field_name("name")?;
10498        return (!name.is_missing() && !body.is_missing())
10499            .then(|| node_text(name, source).to_string());
10500    }
10501    if node.kind() != "ERROR" {
10502        return None;
10503    }
10504
10505    // When an object-like namespace macro is parsed as a function definition,
10506    // tree-sitter can place the entire class declaration inside an ERROR node
10507    // and leave the `class`/`struct` keyword as an anonymous child. Keep the
10508    // fallback structural: accept only a named class-like keyword followed by
10509    // a real body, never an arbitrary identifier mentioned in the envelope.
10510    for index in 0..node.child_count() {
10511        let Some(keyword) = node.child(index) else {
10512            continue;
10513        };
10514        if !matches!(keyword.kind(), "class" | "struct" | "union") {
10515            continue;
10516        }
10517        let mut name = None;
10518        for next_index in (index + 1)..node.child_count() {
10519            let Some(next) = node.child(next_index) else {
10520                continue;
10521            };
10522            if next.kind() == ";" {
10523                break;
10524            }
10525            if next.kind() == "{" {
10526                return name
10527                    .filter(|name_node: &Node<'_>| !name_node.is_missing())
10528                    .map(|name_node| node_text(name_node, source).to_string());
10529            }
10530            if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
10531                name = Some(next);
10532            }
10533        }
10534    }
10535    None
10536}
10537
10538fn recovered_orphaned_namespace_components(
10539    node: Node<'_>,
10540    source: &str,
10541    envelopes: &[OrphanedNamespaceEnvelope],
10542) -> Option<Vec<String>> {
10543    let owner_name = orphaned_using_owner_name(node, source)?;
10544    envelopes
10545        .iter()
10546        .filter(|envelope| {
10547            envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
10548        })
10549        .max_by_key(|envelope| envelope.body_end)
10550        .map(|envelope| envelope.components.clone())
10551}
10552
10553fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
10554    let function = std::iter::successors(node.parent(), |current| current.parent())
10555        .find(|current| current.kind() == "function_definition")?;
10556    let owner = function_definition_owner_lookup_node(function)?;
10557    let scope = owner.child_by_field_name("scope")?;
10558    let mut components = Vec::new();
10559    append_cpp_name_components(scope, source, &mut components)?;
10560    // A qualified out-of-line member definition already carries its namespace
10561    // in the declarator scope (for example `foo::Widget::run`). The recovery
10562    // path is only for parser-orphaned top-level members whose owner scope
10563    // collapsed to the bare class name; requiring that shape prevents an
10564    // earlier, unrelated namespace/class from leaking into a global function's
10565    // using-directive lookup.
10566    if components.len() != 1 {
10567        return None;
10568    }
10569    components.pop()
10570}
10571
10572fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
10573    let started = Instant::now();
10574    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
10575    let source_files = visibility.all_visible_source_files();
10576    if report_stats {
10577        eprintln!(
10578            "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
10579            source_files.len()
10580        );
10581    }
10582    let mut project = ProjectUsingIndex::default();
10583    let mut ordinary_bindings = 0usize;
10584    for source_file in &source_files {
10585        // The per-file index is memoized on the analyzer, so assembling the
10586        // project index for a fresh `VisibilityIndex` copies bindings instead
10587        // of re-walking each file's AST (#1927).
10588        let source_index = visibility
10589            .cpp()
10590            .source_using_index(visibility.token(), source_file);
10591        for (name, bindings) in &source_index.ordinary_by_name {
10592            ordinary_bindings += bindings.len();
10593            project
10594                .ordinary_by_name
10595                .entry(name.clone())
10596                .or_default()
10597                .extend(bindings.iter().cloned());
10598        }
10599        project
10600            .directives
10601            .extend(source_index.directives.iter().cloned());
10602    }
10603    if report_stats {
10604        eprintln!(
10605            "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
10606            source_files.len(),
10607            project.ordinary_by_name.len(),
10608            ordinary_bindings,
10609            project.directives.len(),
10610            started.elapsed().as_millis(),
10611        );
10612    }
10613    project
10614}
10615
10616fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
10617    visibility.project_using_index(|| build_project_using_index(visibility))
10618}
10619
10620/// Build the immutable project-wide using index before a parallel file scan.
10621///
10622/// Keeping the `OnceLock` publication here avoids making every scanner carry
10623/// an eager index, while callers that are about to fan out can prevent one
10624/// worker from doing the whole build as its peers wait on the lock.
10625pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
10626    let _ = project_using_index(visibility);
10627}
10628
10629fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
10630    let (components, global) = match &binding.target {
10631        EffectiveUsingTarget::Ordinary {
10632            target_components,
10633            global,
10634            ..
10635        } => (target_components, *global),
10636        EffectiveUsingTarget::Namespace {
10637            namespace_components,
10638            global,
10639        } => (namespace_components, *global),
10640    };
10641    lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
10642}
10643
10644fn using_binding_target_components_for_name(
10645    binding: &OrdinaryTypeImport,
10646    project: &ProjectUsingIndex,
10647    visibility: &VisibilityIndex<'_>,
10648    file: &ProjectFile,
10649    name: &str,
10650) -> Option<Vec<String>> {
10651    // Built once per call rather than per candidate: the filter runs over every
10652    // visible identifier of `name`, and the source is the same object each time.
10653    let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
10654    let visible_candidates = visibility
10655        .visible_identifier_candidates(file, name)
10656        .filter(|candidate| {
10657            candidate.is_class()
10658                || is_type_alias(candidate)
10659                || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
10660        })
10661        .collect::<Vec<_>>();
10662    if visible_candidates.is_empty() {
10663        return None;
10664    }
10665    match &binding.target {
10666        EffectiveUsingTarget::Ordinary {
10667            name: imported_name,
10668            ..
10669        } if imported_name == name => {
10670            effective_using_target_tiers(binding)
10671                .into_iter()
10672                .find(|qualified| {
10673                    let qualified_name = qualified.join("::");
10674                    visible_candidates
10675                        .iter()
10676                        .any(|candidate| cpp_name_for(candidate) == qualified_name)
10677                })
10678        }
10679        EffectiveUsingTarget::Namespace { .. } => {
10680            visibility.note_using_namespace_lookup_for_test();
10681            let target_tiers = effective_using_target_tiers(binding);
10682            let resolved = target_tiers
10683                .iter()
10684                .find(|namespace_components| {
10685                    let namespace = namespace_components.join("::");
10686                    visible_candidates.iter().any(|candidate| {
10687                        visibility.note_using_name_candidate_inspection_for_test();
10688                        cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
10689                            candidate_namespace == namespace
10690                                || candidate_namespace.starts_with(&format!("{namespace}::"))
10691                        })
10692                    }) || project.directives.iter().any(|candidate| {
10693                        candidate.namespace_scope.as_deref()
10694                            == Some(namespace_components.as_slice())
10695                    }) || project
10696                        .ordinary_by_name
10697                        .values()
10698                        .flatten()
10699                        .any(|candidate| {
10700                            candidate.namespace_scope.as_deref()
10701                                == Some(namespace_components.as_slice())
10702                        })
10703                })
10704                .cloned();
10705            resolved.or_else(|| {
10706                // A sole lexical namespace tier is itself enough to retain the
10707                // directive. Candidate identity is resolved later, where
10708                // target guidance and macro-expanded owner names are available.
10709                // Dropping it here makes an unrelated same-terminal type hide
10710                // the actual namespace member before lookup can compare owners.
10711                (target_tiers.len() == 1)
10712                    .then(|| target_tiers.into_iter().next())
10713                    .flatten()
10714            })
10715        }
10716        EffectiveUsingTarget::Ordinary { .. } => None,
10717    }
10718}
10719
10720fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
10721    let start = activation.checked_sub(1)?;
10722    let mut node = root.descendant_for_byte_range(start, activation)?;
10723    while node.kind() != "preproc_include" {
10724        node = node.parent()?;
10725    }
10726    Some(node)
10727}
10728
10729fn project_using_bindings(
10730    binding: OrdinaryTypeImport,
10731    visibility: &VisibilityIndex<'_>,
10732    file: &ProjectFile,
10733    root: Node<'_>,
10734    source: &str,
10735) -> Vec<OrdinaryTypeImport> {
10736    if binding.source == *file {
10737        return vec![binding];
10738    }
10739    if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
10740        return Vec::new();
10741    }
10742    visibility.note_using_donor_activation_for_test();
10743    let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
10744        return Vec::new();
10745    };
10746    let projections = visibility
10747        .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
10748        .map_or_else(
10749            || {
10750                visibility.conditional_include_projections_for_source(
10751                    file,
10752                    prepared.as_ref(),
10753                    &binding.source,
10754                )
10755            },
10756            |activation_byte| {
10757                Arc::from([ConditionalIncludeProjection {
10758                    activation_byte,
10759                    required_guards: HashSet::default(),
10760                }])
10761            },
10762        );
10763    projections
10764        .iter()
10765        .cloned()
10766        .filter_map(|projection| {
10767            let required_guards =
10768                merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
10769            let mut projected = binding.clone();
10770            projected.required_guards = required_guards;
10771            project_using_binding_at_activation(projected, projection.activation_byte, root, source)
10772        })
10773        .collect()
10774}
10775
10776fn project_using_binding_at_activation(
10777    mut binding: OrdinaryTypeImport,
10778    activation: usize,
10779    root: Node<'_>,
10780    source: &str,
10781) -> Option<OrdinaryTypeImport> {
10782    let include = include_node_for_activation(root, activation)?;
10783    let include_namespace = enclosing_namespace_components(include, source);
10784    let mut declaration_namespace = include_namespace.clone();
10785    declaration_namespace.extend(binding.declaration_namespace);
10786    binding.declaration_namespace = declaration_namespace;
10787    binding.declaration_byte = activation;
10788    if let Some(prefix) = using_named_scope(include, source) {
10789        let mut projected = prefix;
10790        projected.extend(binding.namespace_scope.take().unwrap_or_default());
10791        binding.scope_depth = projected.len();
10792        binding.block_scope = false;
10793        binding.lexical_depth = projected.len();
10794        binding.namespace_scope = Some(projected);
10795        binding.scope_start = 0;
10796        binding.scope_end = usize::MAX;
10797        Some(binding)
10798    } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
10799        binding.namespace_scope = None;
10800        binding.scope_start = start;
10801        binding.scope_end = end;
10802        binding.scope_depth = depth;
10803        binding.block_scope = block_scope;
10804        binding.lexical_depth = include_namespace.len();
10805        Some(binding)
10806    } else {
10807        None
10808    }
10809}
10810
10811/// `node` may be any node of `file`'s tree; the projection reaches the tree
10812/// root itself when it needs one. `Node::parent` re-descends from the root on
10813/// every call (tree-sitter 0.24+), so climbing to the root eagerly at each
10814/// call site cost a near-full-AST scan per reference on a large flat file
10815/// (#1927); a name with no candidate bindings never pays for it.
10816pub fn effective_using_bindings_for_name(
10817    visibility: &VisibilityIndex<'_>,
10818    imports: &OrdinaryTypeImportCell,
10819    file: &ProjectFile,
10820    node: Node<'_>,
10821    source: &str,
10822    name: &str,
10823) -> Arc<[OrdinaryTypeImport]> {
10824    imports
10825        .projection_cell(name)
10826        .get_or_init(|| {
10827            let project = project_using_index(visibility);
10828            let name_bindings = project.ordinary_by_name.get(name);
10829            if name_bindings.is_none() && project.directives.is_empty() {
10830                return Arc::from(Vec::new());
10831            }
10832            let root = root_node(node);
10833            let mut projected = Vec::new();
10834            for binding in name_bindings
10835                .into_iter()
10836                .flatten()
10837                .chain(project.directives.iter())
10838            {
10839                if !visibility.source_is_visible(file, &binding.source) {
10840                    continue;
10841                }
10842                let target_components = using_binding_target_components_for_name(
10843                    binding, project, visibility, file, name,
10844                )
10845                .or_else(|| match &binding.target {
10846                    EffectiveUsingTarget::Ordinary {
10847                        name: imported_name,
10848                        target_components,
10849                        ..
10850                    } if imported_name == name => Some(target_components.clone()),
10851                    EffectiveUsingTarget::Ordinary { .. }
10852                    | EffectiveUsingTarget::Namespace { .. } => None,
10853                });
10854                let Some(target_components) = target_components else {
10855                    continue;
10856                };
10857                let mut binding = binding.clone();
10858                binding.resolved_target_components = Some(target_components);
10859                projected.extend(project_using_bindings(
10860                    binding, visibility, file, root, source,
10861                ));
10862            }
10863            Arc::from(projected)
10864        })
10865        .clone()
10866}
10867
10868pub fn initialized_ordinary_type_imports(
10869    root: Node<'_>,
10870    analyzer: &CppGraphSource<'_>,
10871    visibility: &VisibilityIndex<'_>,
10872    file: &ProjectFile,
10873    source: &str,
10874) -> OrdinaryTypeImportCell {
10875    let cell = visibility.ordinary_type_import_cell(file);
10876    let _ = (root, analyzer, source);
10877    cell
10878}
10879
10880fn root_node(mut node: Node<'_>) -> Node<'_> {
10881    while let Some(parent) = node.parent() {
10882        node = parent;
10883    }
10884    node
10885}
10886
10887/// `reference_guards` is the reference node's guard environment, computed once
10888/// by the caller and shared across every binding: recomputing it per binding
10889/// repeated a full ancestor climb whose every `Node::parent` step re-descends
10890/// from the root (#1927).
10891fn effective_using_binding_active(
10892    binding: &OrdinaryTypeImport,
10893    node: Node<'_>,
10894    lexical_scope: &[String],
10895    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10896    visibility: &VisibilityIndex<'_>,
10897    file: &ProjectFile,
10898) -> bool {
10899    effective_using_binding_guards_active(
10900        binding,
10901        node.start_byte(),
10902        reference_guards,
10903        visibility,
10904        file,
10905    ) && binding.namespace_scope.as_ref().map_or_else(
10906        || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
10907        |namespace| lexical_scope.starts_with(namespace),
10908    )
10909}
10910
10911fn effective_using_binding_guards_active(
10912    binding: &OrdinaryTypeImport,
10913    reference_byte: usize,
10914    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10915    visibility: &VisibilityIndex<'_>,
10916    file: &ProjectFile,
10917) -> bool {
10918    binding.declaration_byte <= reference_byte
10919        && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
10920        && visibility.preprocessor_guards_stable_between(
10921            file,
10922            binding.declaration_byte,
10923            reference_byte,
10924            &binding.required_guards,
10925        )
10926}
10927
10928fn effective_using_binding_guards_compatible(
10929    binding: &OrdinaryTypeImport,
10930    reference_byte: usize,
10931    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10932    visibility: &VisibilityIndex<'_>,
10933    file: &ProjectFile,
10934) -> bool {
10935    binding.source != *file
10936        && !binding.required_guards.is_empty()
10937        && binding.declaration_byte <= reference_byte
10938        && reference_guards.is_some_and(|active| {
10939            !binding.required_guards.is_subset(active)
10940                && merge_preprocessor_guards(&binding.required_guards, active).is_some()
10941        })
10942        && visibility.preprocessor_guards_stable_between(
10943            file,
10944            binding.declaration_byte,
10945            reference_byte,
10946            &binding.required_guards,
10947        )
10948}
10949
10950#[allow(clippy::too_many_arguments)]
10951fn binding_type_candidates(
10952    binding: &OrdinaryTypeImport,
10953    active_bindings: &[&OrdinaryTypeImport],
10954    analyzer: &CppGraphSource<'_>,
10955    visibility: &VisibilityIndex<'_>,
10956    file: &ProjectFile,
10957    name: &str,
10958    direct_target: Option<&CodeUnit>,
10959    reference_byte: usize,
10960) -> Vec<(CodeUnit, Vec<String>)> {
10961    let Some(qualified) = binding.resolved_target_components.clone() else {
10962        return Vec::new();
10963    };
10964    let mut targets = Vec::new();
10965    match binding.target {
10966        EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
10967        EffectiveUsingTarget::Namespace { .. } => {
10968            let mut stack = vec![qualified];
10969            let mut visited = HashSet::default();
10970            while let Some(namespace) = stack.pop() {
10971                if !visited.insert(namespace.clone()) {
10972                    continue;
10973                }
10974                let mut target = namespace.clone();
10975                target.push(name.to_string());
10976                targets.push(target);
10977                stack.extend(active_bindings.iter().filter_map(|candidate| {
10978                    (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
10979                        && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
10980                    .then(|| candidate.resolved_target_components.clone())
10981                    .flatten()
10982                }));
10983            }
10984        }
10985    }
10986    targets
10987        .into_iter()
10988        .flat_map(|target| {
10989            let mut candidates = visibility
10990                .visible_identifier_candidates(file, name)
10991                .filter(|candidate| {
10992                    (candidate.is_class() || is_type_alias(candidate))
10993                        && type_candidate_matches_lookup_components(
10994                            analyzer,
10995                            visibility,
10996                            file,
10997                            candidate,
10998                            reference_byte,
10999                            &target,
11000                        )
11001                })
11002                .cloned()
11003                .collect::<Vec<_>>();
11004            if candidates.is_empty()
11005                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11006                && let Some(target_unit) = direct_target
11007            {
11008                let expanded_target_name = macro_expanded_cpp_name_components(
11009                    visibility,
11010                    file,
11011                    target_unit,
11012                    reference_byte,
11013                );
11014                if (target_unit.is_class() || is_type_alias(target_unit))
11015                    && expanded_target_name == target
11016                    && visibility.external_type_candidate_visible_at(
11017                        file,
11018                        target_unit,
11019                        reference_byte,
11020                    )
11021                {
11022                    candidates.push(target_unit.clone());
11023                }
11024            }
11025            if candidates.is_empty()
11026                && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11027                && let Some(target_unit) = direct_target
11028            {
11029                let visible_types = visibility
11030                    .visible_identifier_candidates(file, name)
11031                    .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
11032                    .collect::<Vec<_>>();
11033                let uniquely_names_target = !visible_types.is_empty()
11034                    && visible_types
11035                        .iter()
11036                        .all(|candidate| same_visible_symbol(candidate, target_unit));
11037                if uniquely_names_target {
11038                    candidates.extend(visible_types.into_iter().cloned());
11039                }
11040            }
11041            candidates
11042                .into_iter()
11043                .map(move |candidate| (candidate, target.clone()))
11044        })
11045        .collect()
11046}
11047
11048fn macro_expanded_cpp_name_components(
11049    visibility: &VisibilityIndex<'_>,
11050    file: &ProjectFile,
11051    unit: &CodeUnit,
11052    reference_byte: usize,
11053) -> Vec<String> {
11054    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11055        brokk_bifrost_core::analyzer::Language::Cpp,
11056        &cpp_name_for(unit),
11057    )
11058    .into_iter()
11059    .flat_map(|component| {
11060        macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11061    })
11062    .collect()
11063}
11064
11065fn macro_expanded_cpp_name_component(
11066    visibility: &VisibilityIndex<'_>,
11067    file: &ProjectFile,
11068    component: String,
11069    reference_byte: usize,
11070) -> Vec<String> {
11071    let Some(replacement) =
11072        visibility.object_macro_replacement_at(file, &component, reference_byte)
11073    else {
11074        return vec![component];
11075    };
11076    let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11077        brokk_bifrost_core::analyzer::Language::Cpp,
11078        &replacement,
11079    );
11080    if expanded.is_empty() {
11081        vec![component]
11082    } else {
11083        expanded
11084    }
11085}
11086
11087/// Whether a visible type has the requested qualified spelling.
11088///
11089/// Members of an inline namespace are also members of its enclosing namespace,
11090/// so an ordinary using-declaration may legally omit the inline component. The
11091/// stored FQ name retains that component to keep declarations distinct. Recover
11092/// the omitted spellings from the candidate declaration's namespace ancestors,
11093/// using CST `inline` tokens rather than guessing from names.
11094fn type_candidate_matches_lookup_components(
11095    analyzer: &CppGraphSource<'_>,
11096    visibility: &VisibilityIndex<'_>,
11097    file: &ProjectFile,
11098    candidate: &CodeUnit,
11099    reference_byte: usize,
11100    target: &[String],
11101) -> bool {
11102    let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11103    if expanded == target {
11104        return true;
11105    }
11106    let Some(cpp) = analyzer.cpp else {
11107        return false;
11108    };
11109    let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11110        return false;
11111    };
11112    let root = prepared.tree().root_node();
11113    for range in analyzer.ranges(candidate) {
11114        let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11115        else {
11116            continue;
11117        };
11118        let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11119        loop {
11120            if current.kind() == "namespace_definition"
11121                && let Some(name) = current.child_by_field_name("name")
11122            {
11123                let mut components = Vec::new();
11124                if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11125                    && !components.is_empty()
11126                {
11127                    let inline = (0..current.child_count())
11128                        .filter_map(|index| current.child(index))
11129                        .any(|child| !child.is_named() && child.kind() == "inline");
11130                    namespaces.push((components, inline));
11131                }
11132            }
11133            let Some(parent) = current.parent() else {
11134                break;
11135            };
11136            current = parent;
11137        }
11138        namespaces.reverse();
11139        let mut namespace_components = Vec::new();
11140        let mut inline_indexes = HashSet::default();
11141        for (components, inline) in namespaces {
11142            for component in components {
11143                let expanded_component =
11144                    macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11145                if inline {
11146                    inline_indexes.extend(
11147                        namespace_components.len()
11148                            ..namespace_components.len() + expanded_component.len(),
11149                    );
11150                }
11151                namespace_components.extend(expanded_component);
11152            }
11153        }
11154        if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11155            continue;
11156        }
11157        // Each inline namespace component can be present or elided. Compare
11158        // those alternatives as a small dynamic program instead of generating
11159        // every subset of a deeply nested inline-namespace chain.
11160        let mut reachable = vec![false; target.len() + 1];
11161        reachable[0] = true;
11162        for (index, component) in expanded.iter().enumerate() {
11163            let mut next = vec![false; target.len() + 1];
11164            for (target_index, reached) in reachable.iter().copied().enumerate() {
11165                if !reached {
11166                    continue;
11167                }
11168                if inline_indexes.contains(&index) {
11169                    next[target_index] = true;
11170                }
11171                if target
11172                    .get(target_index)
11173                    .is_some_and(|target_component| target_component == component)
11174                {
11175                    next[target_index + 1] = true;
11176                }
11177            }
11178            reachable = next;
11179        }
11180        if reachable[target.len()] {
11181            return true;
11182        }
11183    }
11184    false
11185}
11186
11187#[allow(clippy::too_many_arguments)]
11188fn resolved_type_import(
11189    candidates: Vec<(CodeUnit, Vec<String>)>,
11190    lexical_depth: usize,
11191    is_direct: bool,
11192    analyzer: &CppGraphSource<'_>,
11193    visibility: &VisibilityIndex<'_>,
11194    file: &ProjectFile,
11195    direct_target: Option<&CodeUnit>,
11196) -> OrdinaryTypeImportResolution {
11197    let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
11198    for candidate in candidates {
11199        if !logical
11200            .iter()
11201            .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
11202        {
11203            logical.push(candidate);
11204        }
11205    }
11206    let selected = match logical.as_slice() {
11207        [] => return OrdinaryTypeImportResolution::Missing,
11208        [only] => only,
11209        // Several declarations of one FQN in one file are configuration
11210        // spellings of one entity, not competing types (#1845): the imported
11211        // name is unambiguous, only the branch that supplies it depends on the
11212        // build.
11213        several => {
11214            let units = several
11215                .iter()
11216                .map(|(unit, _)| unit)
11217                .collect::<Vec<&CodeUnit>>();
11218            let Some(spelling) = direct_target.and_then(|target| {
11219                visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
11220            }) else {
11221                return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
11222            };
11223            several
11224                .iter()
11225                .find(|(unit, _)| same_symbol(unit, spelling))
11226                .expect("the selected spelling is one of the imported candidates")
11227        }
11228    };
11229    OrdinaryTypeImportResolution::Resolved {
11230        target: selected.0.clone(),
11231        target_components: selected.1.clone(),
11232        lexical_depth,
11233        is_direct,
11234    }
11235}
11236
11237#[allow(clippy::too_many_arguments)]
11238fn ordinary_type_import_resolution(
11239    node: Node<'_>,
11240    components: &[String],
11241    global: bool,
11242    analyzer: &CppGraphSource<'_>,
11243    visibility: &VisibilityIndex<'_>,
11244    imports: &OrdinaryTypeImportCell,
11245    file: &ProjectFile,
11246    source: &str,
11247    lexical_scope: &[String],
11248    direct_target: Option<&CodeUnit>,
11249) -> OrdinaryTypeImportResolution {
11250    if global || components.len() != 1 {
11251        return OrdinaryTypeImportResolution::Missing;
11252    }
11253    let name = &components[0];
11254    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11255    // Guard ancestry climbs the whole ancestor chain, and each `Node::parent`
11256    // step re-descends from the root (#1927). A name with no bindings needs
11257    // none of it, and one environment serves every binding of the reference.
11258    if bindings.is_empty() {
11259        return OrdinaryTypeImportResolution::Missing;
11260    }
11261    let reference_guards = preprocessor_guard_environment(node, source);
11262    let active = bindings
11263        .iter()
11264        .filter(|binding| {
11265            effective_using_binding_active(
11266                binding,
11267                node,
11268                lexical_scope,
11269                reference_guards.as_ref(),
11270                visibility,
11271                file,
11272            )
11273        })
11274        .collect::<Vec<_>>();
11275    let transitive = bindings
11276        .iter()
11277        .filter(|binding| {
11278            effective_using_binding_guards_active(
11279                binding,
11280                node.start_byte(),
11281                reference_guards.as_ref(),
11282                visibility,
11283                file,
11284            ) && (binding.namespace_scope.is_some()
11285                || (binding.scope_start <= node.start_byte()
11286                    && node.end_byte() <= binding.scope_end))
11287        })
11288        .collect::<Vec<_>>();
11289    ordinary_type_import_resolution_for_bindings(
11290        node,
11291        name,
11292        analyzer,
11293        visibility,
11294        file,
11295        lexical_scope,
11296        direct_target,
11297        &active,
11298        &transitive,
11299    )
11300}
11301
11302#[allow(clippy::too_many_arguments)]
11303fn compatible_foreign_type_import_resolution(
11304    node: Node<'_>,
11305    components: &[String],
11306    global: bool,
11307    analyzer: &CppGraphSource<'_>,
11308    visibility: &VisibilityIndex<'_>,
11309    imports: &OrdinaryTypeImportCell,
11310    file: &ProjectFile,
11311    source: &str,
11312    lexical_scope: &[String],
11313    direct_target: Option<&CodeUnit>,
11314) -> OrdinaryTypeImportResolution {
11315    if global || components.len() != 1 {
11316        return OrdinaryTypeImportResolution::Missing;
11317    }
11318    let name = &components[0];
11319    let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
11320    if bindings.is_empty() {
11321        return OrdinaryTypeImportResolution::Missing;
11322    }
11323    let reference_guards = preprocessor_guard_environment(node, source);
11324    let compatible = bindings
11325        .iter()
11326        .filter(|binding| {
11327            effective_using_binding_guards_compatible(
11328                binding,
11329                node.start_byte(),
11330                reference_guards.as_ref(),
11331                visibility,
11332                file,
11333            ) && binding.namespace_scope.as_ref().map_or_else(
11334                || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11335                |namespace| lexical_scope.starts_with(namespace),
11336            )
11337        })
11338        .collect::<Vec<_>>();
11339    let transitive = bindings
11340        .iter()
11341        .filter(|binding| {
11342            effective_using_binding_guards_compatible(
11343                binding,
11344                node.start_byte(),
11345                reference_guards.as_ref(),
11346                visibility,
11347                file,
11348            ) && (binding.namespace_scope.is_some()
11349                || (binding.scope_start <= node.start_byte()
11350                    && node.end_byte() <= binding.scope_end))
11351        })
11352        .collect::<Vec<_>>();
11353    ordinary_type_import_resolution_for_bindings(
11354        node,
11355        name,
11356        analyzer,
11357        visibility,
11358        file,
11359        lexical_scope,
11360        direct_target,
11361        &compatible,
11362        &transitive,
11363    )
11364}
11365
11366#[allow(clippy::too_many_arguments)]
11367fn ordinary_type_import_resolution_for_bindings(
11368    node: Node<'_>,
11369    name: &str,
11370    analyzer: &CppGraphSource<'_>,
11371    visibility: &VisibilityIndex<'_>,
11372    file: &ProjectFile,
11373    lexical_scope: &[String],
11374    direct_target: Option<&CodeUnit>,
11375    active: &[&OrdinaryTypeImport],
11376    transitive: &[&OrdinaryTypeImport],
11377) -> OrdinaryTypeImportResolution {
11378    let mut concrete_depths = active
11379        .iter()
11380        .filter(|binding| binding.namespace_scope.is_none())
11381        .map(|binding| binding.scope_depth)
11382        .collect::<Vec<_>>();
11383    concrete_depths.sort_unstable();
11384    concrete_depths.dedup();
11385    for depth in concrete_depths.into_iter().rev() {
11386        let at_tier = active
11387            .iter()
11388            .copied()
11389            .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
11390        let direct = at_tier
11391            .clone()
11392            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
11393            .flat_map(|binding| {
11394                binding_type_candidates(
11395                    binding,
11396                    transitive,
11397                    analyzer,
11398                    visibility,
11399                    file,
11400                    name,
11401                    direct_target,
11402                    node.start_byte(),
11403                )
11404            })
11405            .collect::<Vec<_>>();
11406        if !direct.is_empty() {
11407            return resolved_type_import(
11408                direct,
11409                lexical_scope.len(),
11410                true,
11411                analyzer,
11412                visibility,
11413                file,
11414                direct_target,
11415            );
11416        }
11417        let directives = at_tier
11418            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
11419            .flat_map(|binding| {
11420                binding_type_candidates(
11421                    binding,
11422                    transitive,
11423                    analyzer,
11424                    visibility,
11425                    file,
11426                    name,
11427                    direct_target,
11428                    node.start_byte(),
11429                )
11430            })
11431            .collect::<Vec<_>>();
11432        if !directives.is_empty() {
11433            return resolved_type_import(
11434                directives,
11435                lexical_scope.len(),
11436                false,
11437                analyzer,
11438                visibility,
11439                file,
11440                direct_target,
11441            );
11442        }
11443    }
11444    for prefix_len in (0..=lexical_scope.len()).rev() {
11445        let tier = &lexical_scope[..prefix_len];
11446        let at_tier = active
11447            .iter()
11448            .copied()
11449            .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
11450        let direct = at_tier
11451            .clone()
11452            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
11453            .flat_map(|binding| {
11454                binding_type_candidates(
11455                    binding,
11456                    transitive,
11457                    analyzer,
11458                    visibility,
11459                    file,
11460                    name,
11461                    direct_target,
11462                    node.start_byte(),
11463                )
11464            })
11465            .collect::<Vec<_>>();
11466        if !direct.is_empty() {
11467            return resolved_type_import(
11468                direct,
11469                prefix_len,
11470                true,
11471                analyzer,
11472                visibility,
11473                file,
11474                direct_target,
11475            );
11476        }
11477        let directives = at_tier
11478            .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
11479            .flat_map(|binding| {
11480                binding_type_candidates(
11481                    binding,
11482                    transitive,
11483                    analyzer,
11484                    visibility,
11485                    file,
11486                    name,
11487                    direct_target,
11488                    node.start_byte(),
11489                )
11490            })
11491            .collect::<Vec<_>>();
11492        if !directives.is_empty() {
11493            return resolved_type_import(
11494                directives,
11495                prefix_len,
11496                false,
11497                analyzer,
11498                visibility,
11499                file,
11500                direct_target,
11501            );
11502        }
11503    }
11504    OrdinaryTypeImportResolution::Missing
11505}
11506
11507#[allow(clippy::too_many_arguments)]
11508pub fn resolve_type_components_lexically_at(
11509    node: Node<'_>,
11510    components: &[String],
11511    global: bool,
11512    analyzer: &CppGraphSource<'_>,
11513    visibility: &VisibilityIndex<'_>,
11514    ordinary_type_imports: &OrdinaryTypeImportCell,
11515    file: &ProjectFile,
11516    source: &str,
11517) -> LexicalTypeResolution {
11518    resolve_type_components_lexically_at_inner(
11519        node,
11520        components,
11521        global,
11522        analyzer,
11523        visibility,
11524        ordinary_type_imports,
11525        file,
11526        source,
11527        None,
11528        false,
11529        false,
11530        false,
11531        None,
11532    )
11533}
11534
11535/// Resolve a type at its lexical reference site while retaining the identity
11536/// of an alias that C++ lookup selects.
11537///
11538/// Forward navigation uses the selected spelling as its destination, whereas
11539/// graph attribution normally canonicalizes an alias to its target. Both
11540/// surfaces must still apply the same ordinary using-declarations, declaration
11541/// order, guard state, and lexical-depth precedence.
11542#[allow(clippy::too_many_arguments)]
11543pub fn resolve_type_components_lexically_at_preserving_alias(
11544    node: Node<'_>,
11545    components: &[String],
11546    global: bool,
11547    analyzer: &CppGraphSource<'_>,
11548    visibility: &VisibilityIndex<'_>,
11549    file: &ProjectFile,
11550    source: &str,
11551) -> LexicalTypeResolution {
11552    let ordinary_type_imports =
11553        initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
11554    resolve_type_components_lexically_at_inner(
11555        node,
11556        components,
11557        global,
11558        analyzer,
11559        visibility,
11560        &ordinary_type_imports,
11561        file,
11562        source,
11563        None,
11564        false,
11565        true,
11566        true,
11567        None,
11568    )
11569}
11570
11571#[allow(clippy::too_many_arguments)]
11572fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
11573    node: Node<'_>,
11574    components: &[String],
11575    global: bool,
11576    analyzer: &CppGraphSource<'_>,
11577    visibility: &VisibilityIndex<'_>,
11578    ordinary_type_imports: &OrdinaryTypeImportCell,
11579    file: &ProjectFile,
11580    source: &str,
11581    scope_cache: Option<&LexicalScopeCache>,
11582) -> LexicalTypeResolution {
11583    resolve_type_components_lexically_at_inner(
11584        node,
11585        components,
11586        global,
11587        analyzer,
11588        visibility,
11589        ordinary_type_imports,
11590        file,
11591        source,
11592        None,
11593        false,
11594        true,
11595        false,
11596        scope_cache,
11597    )
11598}
11599
11600#[allow(clippy::too_many_arguments)]
11601fn resolve_type_components_lexically_at_for_target_with_scope_cache(
11602    node: Node<'_>,
11603    components: &[String],
11604    global: bool,
11605    analyzer: &CppGraphSource<'_>,
11606    visibility: &VisibilityIndex<'_>,
11607    ordinary_type_imports: &OrdinaryTypeImportCell,
11608    file: &ProjectFile,
11609    source: &str,
11610    target: &CodeUnit,
11611    apply_structured_prefilter: bool,
11612    scope_cache: Option<&LexicalScopeCache>,
11613) -> LexicalTypeResolution {
11614    resolve_type_components_lexically_at_inner(
11615        node,
11616        components,
11617        global,
11618        analyzer,
11619        visibility,
11620        ordinary_type_imports,
11621        file,
11622        source,
11623        Some(target),
11624        apply_structured_prefilter,
11625        false,
11626        false,
11627        scope_cache,
11628    )
11629}
11630
11631#[allow(clippy::too_many_arguments)]
11632fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
11633    node: Node<'_>,
11634    components: &[String],
11635    global: bool,
11636    analyzer: &CppGraphSource<'_>,
11637    visibility: &VisibilityIndex<'_>,
11638    ordinary_type_imports: &OrdinaryTypeImportCell,
11639    file: &ProjectFile,
11640    source: &str,
11641    recovered_scope: &[String],
11642) -> LexicalTypeResolution {
11643    resolve_type_components_in_authoritative_scope(
11644        node,
11645        components,
11646        global,
11647        analyzer,
11648        visibility,
11649        ordinary_type_imports,
11650        file,
11651        source,
11652        None,
11653        false,
11654        true,
11655        false,
11656        recovered_scope.to_vec(),
11657    )
11658}
11659
11660#[allow(clippy::too_many_arguments)]
11661fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
11662    node: Node<'_>,
11663    components: &[String],
11664    global: bool,
11665    analyzer: &CppGraphSource<'_>,
11666    visibility: &VisibilityIndex<'_>,
11667    ordinary_type_imports: &OrdinaryTypeImportCell,
11668    file: &ProjectFile,
11669    source: &str,
11670    target: &CodeUnit,
11671    apply_structured_prefilter: bool,
11672    recovered_scope: &[String],
11673) -> LexicalTypeResolution {
11674    resolve_type_components_in_authoritative_scope(
11675        node,
11676        components,
11677        global,
11678        analyzer,
11679        visibility,
11680        ordinary_type_imports,
11681        file,
11682        source,
11683        Some(target),
11684        apply_structured_prefilter,
11685        false,
11686        false,
11687        recovered_scope.to_vec(),
11688    )
11689}
11690
11691#[allow(clippy::too_many_arguments)]
11692fn resolve_type_components_lexically_at_inner(
11693    node: Node<'_>,
11694    components: &[String],
11695    global: bool,
11696    analyzer: &CppGraphSource<'_>,
11697    visibility: &VisibilityIndex<'_>,
11698    ordinary_type_imports: &OrdinaryTypeImportCell,
11699    file: &ProjectFile,
11700    source: &str,
11701    direct_target: Option<&CodeUnit>,
11702    apply_structured_prefilter: bool,
11703    preserve_alias: bool,
11704    allow_compatible_foreign_import: bool,
11705    scope_cache: Option<&LexicalScopeCache>,
11706) -> LexicalTypeResolution {
11707    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11708    let lexical_scope_started = Instant::now();
11709    if report_stats {
11710        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
11711    }
11712    let lexical_scope = if global {
11713        Vec::new()
11714    } else {
11715        match cached_enclosing_lexical_scope_components_with_unresolved_owner(
11716            node,
11717            analyzer,
11718            visibility,
11719            file,
11720            source,
11721            true,
11722            recovered_macro_decorated_declarator_type(node)
11723                == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
11724            scope_cache,
11725        ) {
11726            LexicalScopeResolution::Resolved(scope) => scope,
11727            LexicalScopeResolution::Ambiguous => {
11728                if report_stats {
11729                    eprintln!(
11730                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
11731                        lexical_scope_started.elapsed().as_millis(),
11732                    );
11733                }
11734                return LexicalTypeResolution::Ambiguous;
11735            }
11736            LexicalScopeResolution::Missing => {
11737                if report_stats {
11738                    eprintln!(
11739                        "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
11740                        lexical_scope_started.elapsed().as_millis(),
11741                    );
11742                }
11743                return LexicalTypeResolution::Missing;
11744            }
11745        }
11746    };
11747    if report_stats {
11748        eprintln!(
11749            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
11750            lexical_scope.len(),
11751            lexical_scope_started.elapsed().as_millis(),
11752        );
11753    }
11754    resolve_type_components_lexically_at_scoped(
11755        node,
11756        components,
11757        global,
11758        analyzer,
11759        visibility,
11760        ordinary_type_imports,
11761        file,
11762        source,
11763        direct_target,
11764        apply_structured_prefilter,
11765        preserve_alias,
11766        allow_compatible_foreign_import,
11767        lexical_scope,
11768    )
11769}
11770
11771#[allow(clippy::too_many_arguments)]
11772fn resolve_type_components_lexically_at_scoped(
11773    node: Node<'_>,
11774    components: &[String],
11775    global: bool,
11776    analyzer: &CppGraphSource<'_>,
11777    visibility: &VisibilityIndex<'_>,
11778    ordinary_type_imports: &OrdinaryTypeImportCell,
11779    file: &ProjectFile,
11780    source: &str,
11781    direct_target: Option<&CodeUnit>,
11782    apply_structured_prefilter: bool,
11783    preserve_alias: bool,
11784    allow_compatible_foreign_import: bool,
11785    mut lexical_scope: Vec<String>,
11786) -> LexicalTypeResolution {
11787    if !global
11788        && components.len() == 1
11789        // The two constant-time conditions run before the ancestor climb: each
11790        // `Node::parent` step re-descends from the root (#1927).
11791        && let Some(target) = direct_target
11792        && lexical_scope
11793            .last()
11794            .is_none_or(|last| last != &components[0])
11795        // A recovered class may contain a real member function nested inside
11796        // the malformed outer wrapper (for example tinyxml2's macro-prefixed
11797        // XMLConstHandle). The nearest function_definition is then the member
11798        // itself, so inspect the complete ancestor chain.
11799        && has_malformed_wrapper_function_definition_ancestor(node)
11800        && let Some(indexed_namespace) =
11801            visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
11802        && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
11803    {
11804        lexical_scope = indexed_namespace;
11805    }
11806    resolve_type_components_in_authoritative_scope(
11807        node,
11808        components,
11809        global,
11810        analyzer,
11811        visibility,
11812        ordinary_type_imports,
11813        file,
11814        source,
11815        direct_target,
11816        apply_structured_prefilter,
11817        preserve_alias,
11818        allow_compatible_foreign_import,
11819        lexical_scope,
11820    )
11821}
11822
11823/// Resolve within a scope already proven by recovered syntax.
11824///
11825/// Unlike parser-derived scope, this scope must not be replaced with the
11826/// queried target's namespace: doing so would let target guidance override a
11827/// nearer declaration represented by the recovered syntax.
11828#[allow(clippy::too_many_arguments)]
11829fn resolve_type_components_in_authoritative_scope(
11830    node: Node<'_>,
11831    components: &[String],
11832    global: bool,
11833    analyzer: &CppGraphSource<'_>,
11834    visibility: &VisibilityIndex<'_>,
11835    ordinary_type_imports: &OrdinaryTypeImportCell,
11836    file: &ProjectFile,
11837    source: &str,
11838    direct_target: Option<&CodeUnit>,
11839    apply_structured_prefilter: bool,
11840    preserve_alias: bool,
11841    allow_compatible_foreign_import: bool,
11842    lexical_scope: Vec<String>,
11843) -> LexicalTypeResolution {
11844    if apply_structured_prefilter
11845        && direct_target.is_some()
11846        && !preserve_alias
11847        && !global
11848        && components.len() == 1
11849        && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
11850    {
11851        return LexicalTypeResolution::Missing;
11852    }
11853    // A recovered macro-prefixed return type can share its global spelling
11854    // with aliases from mutually exclusive included headers. C++ lookup uses
11855    // the declaration physically present earlier in this file; the visibility
11856    // index deliberately retains every configuration alternative. Restore
11857    // that precedence only for the exact recovered scope and lexical tier.
11858    if !global
11859        && components.len() == 1
11860        && recovered_macro_decorated_type_node(node).is_some()
11861        && let Some(resolution) = recovered_same_file_type_alias_resolution(
11862            node,
11863            components,
11864            analyzer,
11865            visibility,
11866            file,
11867            direct_target,
11868            &lexical_scope,
11869        )
11870    {
11871        return resolution;
11872    }
11873    if apply_structured_prefilter
11874        && let Some(target) = direct_target
11875        && !preserve_alias
11876        && !visibility.structured_type_reference_may_resolve_to_target(
11877            analyzer,
11878            file,
11879            components,
11880            global,
11881            &lexical_scope,
11882            target,
11883        )
11884    {
11885        return LexicalTypeResolution::Missing;
11886    }
11887    let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11888    let lexical_normal_started = Instant::now();
11889    if report_stats {
11890        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
11891    }
11892    let normal = if preserve_alias {
11893        visibility.resolve_type_components_lexically_for_forward(
11894            analyzer,
11895            file,
11896            components,
11897            global,
11898            &lexical_scope,
11899        )
11900    } else {
11901        direct_target.map_or_else(
11902            || {
11903                visibility.resolve_type_components_lexically(
11904                    analyzer,
11905                    file,
11906                    components,
11907                    global,
11908                    &lexical_scope,
11909                )
11910            },
11911            |target| {
11912                visibility.resolve_type_components_lexically_for_target(
11913                    analyzer,
11914                    file,
11915                    components,
11916                    global,
11917                    &lexical_scope,
11918                    target,
11919                )
11920            },
11921        )
11922    };
11923    if report_stats {
11924        eprintln!(
11925            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
11926            lexical_normal_started.elapsed().as_millis(),
11927        );
11928    }
11929    let normal = match normal {
11930        LexicalTypeResolution::Resolved { ref unit, .. }
11931            if !visibility
11932                .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
11933        {
11934            LexicalTypeResolution::Missing
11935        }
11936        resolution => resolution,
11937    };
11938    let normal_depth = match &normal {
11939        LexicalTypeResolution::Resolved { components, .. } => {
11940            Some(components.len().saturating_sub(1))
11941        }
11942        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
11943    };
11944    // Ordinary using-declarations participate in unqualified lookup at their
11945    // lexical scope. They therefore replace the resolver's terminal/global
11946    // fallback at the same or a shallower depth. A declaration in a more deeply
11947    // nested named scope is the closer lexical result and remains authoritative.
11948    // Ambiguous imports fail closed unless such a closer declaration exists.
11949    let ordinary_import_started = Instant::now();
11950    if report_stats {
11951        eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
11952    }
11953    let ordinary_import_resolution = ordinary_type_import_resolution(
11954        node,
11955        components,
11956        global,
11957        analyzer,
11958        visibility,
11959        ordinary_type_imports,
11960        file,
11961        source,
11962        &lexical_scope,
11963        direct_target,
11964    );
11965    if report_stats {
11966        eprintln!(
11967            "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
11968            ordinary_import_started.elapsed().as_millis(),
11969        );
11970    }
11971    let resolution = match ordinary_import_resolution {
11972        OrdinaryTypeImportResolution::Missing => normal,
11973        OrdinaryTypeImportResolution::Resolved {
11974            lexical_depth,
11975            is_direct,
11976            ..
11977        } if matches!(&normal, LexicalTypeResolution::Ambiguous)
11978            || normal_depth.is_some_and(|depth| {
11979                depth > lexical_depth || (!is_direct && depth == lexical_depth)
11980            }) =>
11981        {
11982            normal
11983        }
11984        OrdinaryTypeImportResolution::Resolved {
11985            target,
11986            target_components,
11987            ..
11988        } => visibility.resolve_imported_type_candidate(
11989            analyzer,
11990            file,
11991            &target,
11992            &target_components,
11993            direct_target,
11994            preserve_alias,
11995        ),
11996        OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
11997            if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
11998        {
11999            normal
12000        }
12001        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12002    };
12003    if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12004        return resolution;
12005    }
12006
12007    // A foreign header can contribute an exact using binding from a build
12008    // configuration that is compatible with, but not implied by, the current
12009    // reference. Forward navigation has no "unproven" result channel, so it
12010    // may still navigate through one unique structured import after ordinary
12011    // lookup misses. Inverse attribution keeps using its target-guided path
12012    // and records the same evidence as unproven (#940).
12013    match compatible_foreign_type_import_resolution(
12014        node,
12015        components,
12016        global,
12017        analyzer,
12018        visibility,
12019        ordinary_type_imports,
12020        file,
12021        source,
12022        &lexical_scope,
12023        None,
12024    ) {
12025        OrdinaryTypeImportResolution::Missing => resolution,
12026        OrdinaryTypeImportResolution::Resolved {
12027            target,
12028            target_components,
12029            ..
12030        } => visibility.resolve_imported_type_candidate(
12031            analyzer,
12032            file,
12033            &target,
12034            &target_components,
12035            None,
12036            true,
12037        ),
12038        OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12039    }
12040}
12041
12042fn recovered_same_file_type_alias_resolution(
12043    node: Node<'_>,
12044    components: &[String],
12045    analyzer: &CppGraphSource<'_>,
12046    visibility: &VisibilityIndex<'_>,
12047    file: &ProjectFile,
12048    direct_target: Option<&CodeUnit>,
12049    lexical_scope: &[String],
12050) -> Option<LexicalTypeResolution> {
12051    debug_assert_eq!(components.len(), 1);
12052    debug_assert!(recovered_macro_decorated_type_node(node).is_some());
12053    let alias_provider = analyzer.type_alias_provider()?;
12054    for qualified in lexical_component_tiers(components, false, lexical_scope) {
12055        let candidates = visibility
12056            .visible_identifier_candidates(file, &components[0])
12057            .filter(|candidate| {
12058                candidate.source() == file
12059                    && canonical_cpp_scope_components(candidate) == qualified
12060                    && visibility
12061                        .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12062            })
12063            .collect::<Vec<_>>();
12064        if candidates.is_empty() {
12065            continue;
12066        }
12067        if candidates
12068            .iter()
12069            .any(|candidate| !alias_provider.is_type_alias(candidate))
12070        {
12071            return None;
12072        }
12073        let unit = if let Some(target) = direct_target {
12074            visibility.unique_type_candidate_preserving_target(
12075                analyzer,
12076                file,
12077                &candidates,
12078                target,
12079            )?
12080        } else {
12081            let first = candidates[0];
12082            if candidates
12083                .iter()
12084                .any(|candidate| !same_visible_symbol(candidate, first))
12085            {
12086                return None;
12087            }
12088            first.clone()
12089        };
12090        return Some(LexicalTypeResolution::Resolved {
12091            unit,
12092            components: qualified,
12093            candidates: candidates.into_iter().cloned().collect(),
12094        });
12095    }
12096    None
12097}
12098
12099fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12100    matches!(
12101        structured_owner_context_resolution(node, ctx),
12102        StructuredOwnerContextResolution::SelfTarget
12103            | StructuredOwnerContextResolution::InheritedTarget
12104    )
12105}
12106
12107/// A bare/`this->` member call whose name resolves, through the enclosing class's base
12108/// hierarchy, to the target member declared on a base (the target owner). This is a
12109/// genuine external usage of the inherited base member rather than a same-type self call.
12110fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12111    let Some(call) = node.parent().filter(|parent| {
12112        parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
12113    }) else {
12114        return matches!(
12115            structured_owner_context_resolution(node, ctx),
12116            StructuredOwnerContextResolution::InheritedTarget
12117        );
12118    };
12119    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12120        return false;
12121    };
12122    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
12123        return false;
12124    };
12125    if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
12126        return false;
12127    }
12128    let Some(arity) = ctx
12129        .visibility
12130        .call_arity_evidence(ctx.file, call, ctx.source)
12131        .exact()
12132    else {
12133        return false;
12134    };
12135    matches!(
12136        resolve_declaring_callable_owner(
12137            &ctx.analyzer,
12138            ctx.visibility,
12139            ctx.file,
12140            cached_declaring_member_owner(&enclosing_owner, ctx),
12141            &ctx.spec.member_name,
12142            arity,
12143        ),
12144        EnclosingMemberOwnerResolution::Owner(owner)
12145            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
12146    )
12147}
12148
12149fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12150    matches!(
12151        structured_owner_context_resolution(node, ctx),
12152        StructuredOwnerContextResolution::NonTarget
12153    )
12154}
12155
12156fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12157    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12158        return false;
12159    };
12160    let mut current = node.parent();
12161    while let Some(parent) = current {
12162        if parent.kind() == "function_definition" {
12163            let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
12164                return false;
12165            };
12166            if let Some(owners) = out_of_line_member_definition_owner(
12167                &ctx.analyzer,
12168                ctx.visibility,
12169                ctx.file,
12170                ctx.source,
12171                owner_lookup,
12172            ) && let Some((_, owner)) = owners.innermost()
12173            {
12174                return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
12175            }
12176            if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
12177                return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
12178            }
12179            return false;
12180        }
12181        current = parent.parent();
12182    }
12183    false
12184}
12185
12186#[derive(Clone, Copy)]
12187enum StructuredOwnerContextResolution {
12188    /// The enclosing class is itself the target owner: a bare/`this->` call here is a
12189    /// genuine same-type self call (the SelfReceiver policy from #1014-B applies).
12190    SelfTarget,
12191    /// The enclosing class does not declare the member but inherits it from a base that
12192    /// is the target owner. A bare/`this->` call to that inherited member is a genuine
12193    /// external usage OF the base member (e.g. `Derived` calling inherited `Base::value`),
12194    /// not a self call, so it is attributed as an ordinary Reference.
12195    InheritedTarget,
12196    NonTarget,
12197    Ambiguous,
12198    Missing,
12199}
12200
12201fn structured_owner_context_resolution(
12202    node: Node<'_>,
12203    ctx: &ScanCtx<'_>,
12204) -> StructuredOwnerContextResolution {
12205    let Some(target_owner) = ctx.spec.owner.as_ref() else {
12206        return StructuredOwnerContextResolution::Missing;
12207    };
12208    let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
12209        return StructuredOwnerContextResolution::Missing;
12210    };
12211    if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
12212        return StructuredOwnerContextResolution::SelfTarget;
12213    }
12214    // The enclosing class is not the target owner, so any match reached by walking its
12215    // base hierarchy is an inherited-member usage of the base, not a self call.
12216    let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
12217    match member_owner {
12218        EnclosingMemberOwnerResolution::Owner(owner)
12219            if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
12220        {
12221            StructuredOwnerContextResolution::InheritedTarget
12222        }
12223        EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
12224        EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
12225        EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
12226    }
12227}
12228
12229fn cached_declaring_member_owner(
12230    receiver_owner: &CodeUnit,
12231    ctx: &ScanCtx<'_>,
12232) -> EnclosingMemberOwnerResolution {
12233    if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
12234        return cached;
12235    }
12236    let resolved = resolve_declaring_member_owner(
12237        &ctx.analyzer,
12238        ctx.visibility,
12239        ctx.file,
12240        receiver_owner,
12241        &ctx.spec.member_name,
12242    );
12243    let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
12244        indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
12245    } else {
12246        resolved
12247    };
12248    ctx.member_owner_cache
12249        .borrow_mut()
12250        .insert(receiver_owner.clone(), resolved.clone());
12251    resolved
12252}
12253
12254/// Recover a member's declaring owner when parser recovery omitted its
12255/// in-class declaration but retained an out-of-line definition. Ordinary
12256/// visible-member lookup runs first. The structured definition index then
12257/// supplies the missing member fact at each hierarchy level, so an indexed
12258/// derived override still hides the queried base member and distinct base
12259/// paths still fail closed.
12260fn indexed_declaring_owner_for_recovered_member(
12261    receiver_owner: &CodeUnit,
12262    ctx: &ScanCtx<'_>,
12263) -> EnclosingMemberOwnerResolution {
12264    let Some(spec_owner) = ctx.spec.owner.as_ref() else {
12265        return EnclosingMemberOwnerResolution::Missing;
12266    };
12267    if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
12268        return EnclosingMemberOwnerResolution::Missing;
12269    }
12270    let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
12271        return EnclosingMemberOwnerResolution::Missing;
12272    };
12273    let Some(receiver_owner) =
12274        ctx.visibility
12275            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
12276    else {
12277        return EnclosingMemberOwnerResolution::Ambiguous;
12278    };
12279    let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
12280        ctx.visibility
12281            .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
12282    }) else {
12283        return EnclosingMemberOwnerResolution::Missing;
12284    };
12285
12286    let owner_declares_member = |owner: &CodeUnit| {
12287        if same_visible_symbol(owner, &target_owner) {
12288            return true;
12289        }
12290        let mut member_fq = owner.fq().clone();
12291        member_fq.push(
12292            ctx.spec
12293                .target
12294                .fq()
12295                .last()
12296                .expect("a method target has a terminal member segment"),
12297        );
12298        ctx.analyzer
12299            .definitions(&member_fq.display(segment_interner()))
12300            .any(|child| child.is_function())
12301    };
12302    if owner_declares_member(&receiver_owner) {
12303        return EnclosingMemberOwnerResolution::Owner(receiver_owner);
12304    }
12305
12306    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
12307    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
12308    let mut declaring_owner = None;
12309    while let Some(raw_owner) = stack.pop() {
12310        let Some(owner) =
12311            ctx.visibility
12312                .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
12313        else {
12314            return EnclosingMemberOwnerResolution::Ambiguous;
12315        };
12316        let propagated = propagated_counts.entry(owner.clone()).or_default();
12317        if *propagated == 2 {
12318            continue;
12319        }
12320        *propagated += 1;
12321        if owner_declares_member(&owner) {
12322            if declaring_owner.is_some() {
12323                return EnclosingMemberOwnerResolution::Ambiguous;
12324            }
12325            declaring_owner = Some(owner);
12326            continue;
12327        }
12328        stack.extend(hierarchy.get_direct_ancestors(&owner));
12329    }
12330    declaring_owner
12331        .map(EnclosingMemberOwnerResolution::Owner)
12332        .unwrap_or(EnclosingMemberOwnerResolution::Missing)
12333}
12334
12335fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
12336    // Declaration recovery can index the true class/member ranges even when
12337    // the original error tree wraps that region in a bogus function. Prefer
12338    // the analyzer's exact enclosing-owner graph at the reference byte before
12339    // interpreting such a wrapper as a real callable owner.
12340    if (has_recovered_class_shape_ancestor(node)
12341        || has_malformed_wrapper_function_definition_ancestor(node))
12342        && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
12343    {
12344        return Some(owner);
12345    }
12346    let mut current = node.parent();
12347    while let Some(parent) = current {
12348        if parent.kind() == "function_definition" {
12349            let owner_lookup = function_definition_owner_lookup_node(parent);
12350            if let Some(owner_lookup) = owner_lookup
12351                && let Some(owners) = out_of_line_member_definition_owner(
12352                    &ctx.analyzer,
12353                    ctx.visibility,
12354                    ctx.file,
12355                    ctx.source,
12356                    owner_lookup,
12357                )
12358                && let Some((_, owner)) = owners.innermost()
12359            {
12360                return Some(owner.clone());
12361            }
12362            if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
12363                return Some(owner);
12364            }
12365            if let Some(owner) = enclosing_context(parent, ctx)
12366                .owner
12367                .filter(|owner| owner.is_class())
12368            {
12369                return Some(owner);
12370            }
12371            if let Some(owner_lookup) = owner_lookup
12372                && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
12373            {
12374                return Some(owner);
12375            }
12376            break;
12377        }
12378        current = parent.parent();
12379    }
12380    enclosing_context(node, ctx)
12381        .owner
12382        .filter(|owner| owner.is_class())
12383}
12384
12385fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
12386    let target_owner = ctx.spec.owner.as_ref()?;
12387    let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
12388    let owner_name = owner_components.last()?;
12389    let mut candidates = Vec::new();
12390    for candidate in ctx
12391        .visibility
12392        .visible_identifier_candidates(ctx.file, owner_name)
12393        .filter(|candidate| candidate.is_class())
12394    {
12395        let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12396            brokk_bifrost_core::analyzer::Language::Cpp,
12397            &cpp_name_for(candidate),
12398        );
12399        if !components.ends_with(&owner_components)
12400            || candidates
12401                .iter()
12402                .any(|existing| same_logical_symbol(existing, candidate))
12403        {
12404            continue;
12405        }
12406        candidates.push(candidate.clone());
12407    }
12408    let [candidate] = candidates.as_slice() else {
12409        return None;
12410    };
12411    (same_logical_symbol(candidate, target_owner)
12412        && target_group_contains_owner_peer(candidate, ctx))
12413    .then(|| candidate.clone())
12414}