Skip to main content

brokk_bifrost_cpp/graph/
inverted.rs

1//! Whole-workspace inverted edge builder for C++.
2//!
3//! Walks each file once and resolves every reference to the callee fqn it names,
4//! via the shared `build_edges` driver in `brokk-bifrost-analysis`. C++ node fqns
5//! are dotted: a namespace +
6//! class + member reads `example.Service.run`, a free function `example.freeHelper`,
7//! and a class `example.Service`. References resolve through the forward scanner's
8//! visibility primitives ([`VisibilityIndex::resolve_type`] / `resolve_named`,
9//! which honor the include closure and namespaces) plus a [`LocalInferenceEngine`]
10//! (typed by [`CodeUnit`], like the forward scan) seeded with every local's and
11//! parameter's declared type so a method call's receiver can be typed:
12//!
13//! - a type reference (`Foo x`, `new Foo()`, a base class) resolves to the class;
14//! - `recv.m(..)` / `recv->m(..)` (`field_expression` under a call) types `recv`
15//!   and gives `Owner.m`;
16//! - `X::m(..)` (`qualified_identifier`) resolves `X` and gives `Owner.m`;
17//! - a bare `m(..)` is a free function (`Namespace.m`); `this->m(..)` and other
18//!   unqualified member calls attribute to the enclosing class;
19//! - a chained receiver (`p->get()->m()`) follows the uniquely resolved persisted
20//!   callable return type before recording `Owner.m`.
21//!
22//! The enclosing class is taken from a per-file class-range index (the analyzer's
23//! own fqns), so `this->`/unqualified calls attribute to the right class without
24//! re-deriving the namespace. Ambiguous receiver or return identities fail closed.
25
26use crate::declarations::{
27    CppSentinelRecoveredClass, cpp_sentinel_recovered_classes,
28    cpp_sentinel_recovered_scope_for_node, node_text, recovered_macro_return_type_node,
29};
30use crate::graph::CppGraphSource;
31use crate::graph::extractor::{
32    BareCallTargetResolution, LexicalScopeResolution, enclosing_lexical_scope_components,
33    has_persisted_global_field_identity, initialized_ordinary_type_imports,
34    ordinary_using_declaration_type_node, resolve_bare_call_target,
35    resolve_ordinary_using_declaration_owner, resolve_type_components_lexically_at,
36    resolve_type_node_lexically, resolve_using_enum_declaration_owner,
37    using_enum_declaration_type_node,
38};
39use crate::graph::resolver::field_declared_binding;
40use crate::graph::resolver::{
41    CppTemplateResolutionError, DesignatedInitializerOwner, EnclosingMemberOwnerResolution,
42    LexicalCallableValueResolution, LexicalTypeResolution, OrdinaryMacroReferenceResolution,
43    OrdinaryTypeImportCell, TargetKind, VisibilityIndex, VisibleMemberResolution,
44    anonymous_aggregate_owner, c_offsetof_member_parts, canonical_cpp_scope_components,
45    cast_expression_type_node, constructor_style_local_declaration, cpp_callable_arity,
46    cpp_field_expression_receiver, cpp_template_reference_arguments, cpp_type_name_components,
47    declarator_name_node, designated_initializer_owner, extract_variable_name, first_type_child,
48    function_terminal_node, has_ancestor_kind, infer_cpp_initializer_binding,
49    infer_cpp_initializer_type, initialized_type_declaration_with_cast, is_c_offsetof_member_node,
50    is_c_sizeof_expression_type_candidate, is_cpp_template_argument_type_leaf, is_declaration_name,
51    is_declarator_node, is_globally_qualified_cpp_name, is_nested_type_node,
52    is_recovered_qualified_friend_class_type_reference, normalize_type_text,
53    out_of_line_member_definition_owner, parameter_belongs_to_callable_scope,
54    qualified_alias_reference_preserves_target, qualified_alias_reference_requires_terminal,
55    qualified_owner_components, recovered_c_new_expression_argument_at,
56    recovered_macro_decorated_type_node, recovered_relational_template_member_call,
57    resolve_declaring_callable_owner, resolve_declaring_member_owner, same_logical_symbol,
58    same_visible_global_field_symbol, same_visible_symbol, type_reference_hit_node,
59    unique_macro_replacement_type_candidate,
60};
61use crate::graph::syntax::{object_macro_replacement_type_references, qualified_callable_value};
62use brokk_bifrost_core::analyzer::tree_walk::{TreeWalkAction, walk_tree_iterative};
63use brokk_bifrost_core::analyzer::usages::common::same_node;
64use brokk_bifrost_core::analyzer::usages::inverted_edges::{
65    ClassRangeIndex, FileEdgeScanInput, PerFileEdges, classify_reference_node, first_precise,
66};
67use brokk_bifrost_core::analyzer::usages::local_inference::{
68    LocalInferenceConfig, LocalInferenceEngine,
69};
70use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile};
71use brokk_bifrost_core::hash::{HashMap, HashSet};
72use tree_sitter::Node;
73
74/// The C++ half of one file's inverted pass: seed the scan context from the
75/// already-parsed tree and walk it, recording every `caller -> callee` edge the
76/// file names.
77///
78/// The pass's fan-out -- `build_edge_output` plus `parse_and_collect`, the
79/// shared language-agnostic driver -- stays in `brokk-bifrost-analysis` and
80/// calls this once per kept file.
81pub fn scan_file(
82    analyzer: &CppGraphSource<'_>,
83    visibility: &VisibilityIndex<'_>,
84    file: &ProjectFile,
85    input: &FileEdgeScanInput<'_>,
86) -> PerFileEdges {
87    let ordinary_type_imports =
88        initialized_ordinary_type_imports(input.root(), analyzer, visibility, file, input.source);
89    let recovered_sentinel_classes = cpp_sentinel_recovered_classes(input.root(), input.source);
90    let mut ctx = CppScan {
91        analyzer: *analyzer,
92        visibility,
93        file,
94        source: input.source,
95        ordinary_type_imports,
96        recovered_sentinel_classes,
97        class_ranges: ClassRangeIndex::build(analyzer.index, file),
98        declaring_member_cache: HashMap::default(),
99        input,
100        edges: PerFileEdges::default(),
101    };
102    let mut bindings = LocalInferenceEngine::new(LocalInferenceConfig::default());
103    walk(input.root(), &mut ctx, &mut bindings);
104    ctx.edges
105}
106
107struct CppScan<'a> {
108    analyzer: CppGraphSource<'a>,
109    visibility: &'a VisibilityIndex<'a>,
110    file: &'a ProjectFile,
111    source: &'a str,
112    ordinary_type_imports: OrdinaryTypeImportCell,
113    recovered_sentinel_classes: Vec<CppSentinelRecoveredClass>,
114    class_ranges: ClassRangeIndex,
115    declaring_member_cache: HashMap<CodeUnit, HashMap<String, EnclosingMemberOwnerResolution>>,
116    input: &'a FileEdgeScanInput<'a>,
117    edges: PerFileEdges,
118}
119
120impl CppScan<'_> {
121    /// Resolve a type reference's text to a class `CodeUnit`.
122    fn resolve_type(&self, text: &str) -> Option<CodeUnit> {
123        self.visibility.resolve_type(self.file, text)
124    }
125
126    fn resolve_type_node_result(
127        &self,
128        node: Node<'_>,
129    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
130        self.visibility
131            .resolve_type_node_result(self.file, node, self.source)
132    }
133
134    /// The fqn of the smallest class declaration containing `byte`.
135    fn enclosing_class(&self, byte: usize) -> Option<&str> {
136        self.class_ranges.enclosing(byte)
137    }
138
139    /// Return the smallest recovered sentinel owner scope containing `node`.
140    /// Out-of-line definitions can sit beyond the recovered class range, so
141    /// prefer an owner span over the class itself.  For references in a class
142    /// body, append any parser-visible nested class names to the recovered
143    /// top-level class path; the declaration visitor uses the same AST nesting
144    /// when it re-owns those members.
145    fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
146        cpp_sentinel_recovered_scope_for_node(node, self.source, &self.recovered_sentinel_classes)
147    }
148
149    fn record(&mut self, callee: String, node: Node<'_>) {
150        self.edges.record_kind(
151            self.input,
152            callee,
153            classify_reference_node(node),
154            node.start_byte(),
155            node.end_byte(),
156        );
157    }
158
159    fn record_range(&mut self, callee: String, anchor: Node<'_>, start: usize, end: usize) {
160        self.edges.record_kind(
161            self.input,
162            callee,
163            classify_reference_node(anchor),
164            start,
165            end,
166        );
167    }
168
169    fn record_unproven(&mut self, name: &str, node: Node<'_>) {
170        self.edges
171            .record_unproven_name(self.input, name, node.start_byte(), node.end_byte());
172    }
173}
174
175const SCOPE_NODES: &[&str] = &[
176    "compound_statement",
177    "field_declaration_list",
178    "function_definition",
179    "for_range_loop",
180    "lambda_expression",
181    "for_statement",
182    "while_statement",
183    "if_statement",
184];
185
186fn walk(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &mut LocalInferenceEngine<CodeUnit>) {
187    let mut state = (ctx, bindings);
188    walk_tree_iterative(
189        node,
190        &mut state,
191        |node, (ctx, bindings)| {
192            if walk_enter(node, ctx, bindings) {
193                TreeWalkAction::DescendWithExit
194            } else {
195                TreeWalkAction::Descend
196            }
197        },
198        |(_, bindings)| bindings.exit_scope(),
199    );
200}
201
202fn walk_enter(
203    node: Node<'_>,
204    ctx: &mut CppScan<'_>,
205    bindings: &mut LocalInferenceEngine<CodeUnit>,
206) -> bool {
207    let enters_scope = SCOPE_NODES.contains(&node.kind());
208    if enters_scope {
209        bindings.enter_scope();
210    }
211    seed_declaration(node, ctx, bindings);
212    record_reference(node, ctx, bindings);
213    enters_scope
214}
215
216fn record_reference(
217    node: Node<'_>,
218    ctx: &mut CppScan<'_>,
219    bindings: &LocalInferenceEngine<CodeUnit>,
220) {
221    if recovered_c_new_expression_argument_at(
222        node,
223        node.start_byte(),
224        node.end_byte(),
225        ctx.analyzer.reference_uses_c_semantics(ctx.file),
226    )
227    .is_some()
228    {
229        return;
230    }
231    if node.kind() == "preproc_arg" {
232        record_object_macro_replacement_type_references(node, ctx);
233    }
234    if let Some(type_node) = ctx
235        .visibility
236        .function_macro_type_argument(ctx.file, node, ctx.source)
237    {
238        let name = node_text(type_node, ctx.source);
239        if bindings.is_shadowed(name) {
240            return;
241        }
242        match resolve_type_node_lexically(
243            type_node,
244            &ctx.analyzer,
245            ctx.visibility,
246            &ctx.ordinary_type_imports,
247            ctx.file,
248            ctx.source,
249        ) {
250            LexicalTypeResolution::Resolved { unit, .. } => {
251                ctx.record(unit.fq_name(), type_node);
252            }
253            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
254                ctx.record_unproven(name, type_node);
255            }
256        }
257        return;
258    }
259    if node
260        .parent()
261        .is_some_and(|parent| parent.kind() == "destructor_name")
262    {
263        return;
264    }
265    match ctx
266        .visibility
267        .resolve_ordinary_macro_reference(&ctx.analyzer, ctx.file, node, ctx.source)
268    {
269        OrdinaryMacroReferenceResolution::Resolved(macro_unit) => {
270            ctx.record(macro_unit.fq_name(), node);
271            return;
272        }
273        OrdinaryMacroReferenceResolution::Ambiguous => {
274            ctx.record_unproven(node_text(node, ctx.source), node);
275            return;
276        }
277        OrdinaryMacroReferenceResolution::Missing => {}
278    }
279    if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
280        record_recovered_macro_return_type_reference(return_type, ctx);
281        return;
282    }
283    if let Some(call) = recovered_relational_template_member_call(node) {
284        record_recovered_relational_template_member_call(call, ctx, bindings);
285        return;
286    }
287    if node.kind() == "using_declaration" {
288        let (resolution, type_node) =
289            if let Some(type_node) = using_enum_declaration_type_node(node) {
290                (
291                    resolve_using_enum_declaration_owner(
292                        node,
293                        &ctx.analyzer,
294                        ctx.visibility,
295                        &ctx.ordinary_type_imports,
296                        ctx.file,
297                        ctx.source,
298                    ),
299                    type_node,
300                )
301            } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
302                (
303                    resolve_ordinary_using_declaration_owner(
304                        node,
305                        &ctx.analyzer,
306                        ctx.visibility,
307                        ctx.file,
308                        ctx.source,
309                    ),
310                    type_node,
311                )
312            } else {
313                return;
314            };
315        match resolution {
316            LexicalTypeResolution::Resolved { unit, .. } => ctx.record(unit.fq_name(), type_node),
317            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
318                ctx.record_unproven(node_text(type_node, ctx.source), type_node);
319            }
320        }
321        return;
322    }
323    if has_ancestor_kind(node, "using_declaration") {
324        return;
325    }
326    if is_c_offsetof_member_node(node) {
327        record_c_offsetof_field_reference(node, ctx);
328        return;
329    }
330    if ctx.analyzer.reference_uses_c_semantics(ctx.file)
331        && node.kind() == "field_expression"
332        && !node.parent().is_some_and(|parent| {
333            parent.kind() == "call_expression"
334                && parent.child_by_field_name("function") == Some(node)
335        })
336    {
337        record_c_field_reference(node, ctx, bindings);
338        return;
339    }
340    if is_c_sizeof_expression_type_candidate(ctx.file, node) {
341        let name = node_text(node, ctx.source);
342        if bindings.is_shadowed(name) {
343            return;
344        }
345        match ctx.resolve_type_node_result(node) {
346            Ok(Some(unit)) => ctx.record(unit.fq_name(), node),
347            Ok(None) | Err(_) => ctx.record_unproven(name, node),
348        }
349        return;
350    }
351    if matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
352        && is_declaration_name(node)
353        && !is_recovered_qualified_friend_class_type_reference(node, ctx.source)
354        && let Some(owners) = out_of_line_member_definition_owner(
355            &ctx.analyzer,
356            ctx.visibility,
357            ctx.file,
358            ctx.source,
359            node,
360        )
361    {
362        for (owner_node, owner) in owners.owners {
363            ctx.record(owner.fq_name(), owner_node);
364        }
365        return;
366    }
367    if let Some(value) = qualified_callable_value(node) {
368        record_qualified_callable_value(
369            value.qualified,
370            value.global,
371            &value.owner_components,
372            value.member,
373            ctx,
374        );
375        return;
376    }
377    if matches!(node.kind(), "identifier" | "field_identifier")
378        && let Some(designator_owner) =
379            designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
380    {
381        let name = node_text(node, ctx.source);
382        match designator_owner {
383            DesignatedInitializerOwner::Resolved(owner) => {
384                if let Some(field) = ctx
385                    .visibility
386                    .visible_members_for_owner_name(ctx.file, &owner, name)
387                    .into_iter()
388                    .find(|unit| unit.is_field())
389                {
390                    ctx.record(field.fq_name(), node);
391                }
392            }
393            DesignatedInitializerOwner::Unresolved => ctx.record_unproven(name, node),
394        }
395        return;
396    }
397    // C field reads are value references, not callable edges.  The C++ graph
398    // deliberately omits ordinary data-member edges, but C census sites need
399    // the same structured field identity that the forward resolver exposes.
400    // Let `record_call` own a field expression used as a call callee.
401    if ctx.analyzer.reference_uses_c_semantics(ctx.file)
402        && node.kind() == "field_expression"
403        && !node.parent().is_some_and(|parent| {
404            parent.kind() == "call_expression"
405                && parent.child_by_field_name("function") == Some(node)
406        })
407    {
408        record_c_field_reference(node, ctx, bindings);
409        return;
410    }
411    match node.kind() {
412        "namespace_identifier"
413            if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) =>
414        {
415            record_recovered_macro_decorated_type_reference(node, type_node, ctx);
416        }
417        // A type reference (`Foo x`, base class, `new Foo()`'s type child) resolves
418        // to the class. `new Foo()` reaches its type via this case (its type child
419        // is itself one of these nodes), so there is no separate construction case.
420        "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type" => {
421            if is_declaration_name(node)
422                && !is_recovered_qualified_friend_class_type_reference(node, ctx.source)
423            {
424                if let Some(owners) = out_of_line_member_definition_owner(
425                    &ctx.analyzer,
426                    ctx.visibility,
427                    ctx.file,
428                    ctx.source,
429                    node,
430                ) {
431                    for (owner_node, owner) in owners.owners {
432                        ctx.record(owner.fq_name(), owner_node);
433                    }
434                }
435                return;
436            }
437            if is_nested_type_node(node) && !is_cpp_template_argument_type_leaf(node) {
438                record_nested_type_terminal_reference(node, ctx);
439                return;
440            }
441            // A `X::m(..)` static/scoped call appears as a `qualified_identifier`
442            // function: resolve the `X` qualifier as a type and emit `Owner.m`.
443            if let Some(function) = scoped_free_function(node, ctx) {
444                ctx.record(function.fq_name(), function_terminal_node(node));
445                return;
446            }
447            if let Some(call) = node.parent().filter(|parent| {
448                parent.kind() == "call_expression"
449                    && parent.child_by_field_name("function") == Some(node)
450            }) && let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
451                node,
452                &ctx.analyzer,
453                ctx.visibility,
454                &ctx.ordinary_type_imports,
455                ctx.file,
456                ctx.source,
457            ) {
458                let Some(call_arity) = ctx
459                    .visibility
460                    .call_arity_evidence(ctx.file, call, ctx.source)
461                    .exact()
462                else {
463                    ctx.record_unproven(node_text(node, ctx.source), function_terminal_node(node));
464                    return;
465                };
466                if let VisibleMemberResolution::Callable(constructors) = ctx
467                    .visibility
468                    .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
469                    && let Some(constructor) = constructors.iter().find(|constructor| {
470                        cpp_callable_arity(&ctx.analyzer, constructor)
471                            .is_none_or(|known| known.accepts(call_arity))
472                    })
473                {
474                    ctx.record(constructor.fq_name(), node);
475                } else {
476                    ctx.record(unit.fq_name(), node);
477                }
478                return;
479            }
480            if let Some(owner) = scoped_call_owner(node, ctx) {
481                let member = scoped_call_member(node, ctx.source);
482                if !member.is_empty() {
483                    ctx.record(format!("{owner}.{member}"), function_terminal_node(node));
484                    return;
485                }
486            }
487            record_type_reference(node, ctx);
488        }
489        "call_expression" => record_call(node, ctx, bindings),
490        _ => {}
491    }
492}
493
494fn record_c_offsetof_field_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
495    let name = node_text(node, ctx.source);
496    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
497        ctx.record_unproven(name, node);
498        return;
499    };
500    let owner = match resolve_type_node_lexically(
501        type_reference,
502        &ctx.analyzer,
503        ctx.visibility,
504        &ctx.ordinary_type_imports,
505        ctx.file,
506        ctx.source,
507    ) {
508        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
509        LexicalTypeResolution::Resolved { .. }
510        | LexicalTypeResolution::Ambiguous
511        | LexicalTypeResolution::Missing => {
512            ctx.record_unproven(name, member);
513            return;
514        }
515    };
516    let candidates = ctx
517        .visibility
518        .visible_members_for_owner_name(ctx.file, &owner, name)
519        .into_iter()
520        .filter(|candidate| candidate.is_field())
521        .collect::<Vec<_>>();
522    if candidates.len() == 1 {
523        ctx.record(candidates[0].fq_name(), member);
524    } else if candidates.len() > 1 {
525        ctx.record_unproven(name, member);
526    }
527}
528fn record_c_field_reference(
529    node: Node<'_>,
530    ctx: &mut CppScan<'_>,
531    bindings: &LocalInferenceEngine<CodeUnit>,
532) {
533    let Some(field) = node.child_by_field_name("field") else {
534        return;
535    };
536    let name = node_text(field, ctx.source);
537    if name.is_empty() {
538        return;
539    }
540    let Some(receiver) = cpp_field_expression_receiver(node) else {
541        ctx.record_unproven(name, field);
542        return;
543    };
544    let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) else {
545        ctx.record_unproven(name, field);
546        return;
547    };
548    let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
549        EnclosingMemberOwnerResolution::Owner(owner) => owner,
550        EnclosingMemberOwnerResolution::Ambiguous => {
551            ctx.record_unproven(name, field);
552            return;
553        }
554        EnclosingMemberOwnerResolution::Missing => return,
555    };
556    let fields = ctx
557        .visibility
558        .visible_members_for_owner_name(ctx.file, &owner, name)
559        .into_iter()
560        .filter(|candidate| {
561            candidate.is_field()
562                && (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
563                    || ctx.visibility.declaration_visible_at_reference(
564                        &ctx.analyzer,
565                        ctx.file,
566                        candidate,
567                        field,
568                    ))
569        })
570        .collect::<Vec<_>>();
571    match fields.as_slice() {
572        [candidate] => ctx.record(candidate.fq_name(), field),
573        [] => {}
574        _ => {
575            if fields
576                .iter()
577                .skip(1)
578                .all(|candidate| same_visible_symbol(candidate, fields[0]))
579            {
580                ctx.record(fields[0].fq_name(), field);
581            } else {
582                ctx.record_unproven(name, field);
583            }
584        }
585    }
586}
587
588fn record_object_macro_replacement_type_references(node: Node<'_>, ctx: &mut CppScan<'_>) {
589    for reference in object_macro_replacement_type_references(node, ctx.source) {
590        for component_count in 1..=reference.components.len() {
591            let resolution = resolve_type_components_lexically_at(
592                node,
593                &reference.components[..component_count],
594                reference.global,
595                &ctx.analyzer,
596                ctx.visibility,
597                &ctx.ordinary_type_imports,
598                ctx.file,
599                ctx.source,
600            );
601            let unit = match resolution {
602                LexicalTypeResolution::Resolved { unit, .. } => unit,
603                LexicalTypeResolution::Missing => {
604                    let Some(unit) = unique_macro_replacement_type_candidate(
605                        &ctx.analyzer,
606                        ctx.visibility,
607                        ctx.file,
608                        &reference.components[..component_count],
609                    ) else {
610                        continue;
611                    };
612                    unit
613                }
614                LexicalTypeResolution::Ambiguous => continue,
615            };
616            let range = &reference.component_ranges[component_count - 1];
617            ctx.record_range(unit.fq_name(), node, range.start, range.end);
618        }
619    }
620}
621
622fn record_recovered_relational_template_member_call(
623    call: crate::graph::resolver::RecoveredRelationalTemplateMemberCall<'_>,
624    ctx: &mut CppScan<'_>,
625    bindings: &LocalInferenceEngine<CodeUnit>,
626) {
627    let name = node_text(call.member, ctx.source);
628    let Some(receiver_owner) = receiver_type_unit(call.receiver, ctx, bindings, 32) else {
629        ctx.record_unproven(name, call.member);
630        return;
631    };
632    let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
633        EnclosingMemberOwnerResolution::Owner(owner) => owner,
634        EnclosingMemberOwnerResolution::Ambiguous => {
635            ctx.record_unproven(name, call.member);
636            return;
637        }
638        EnclosingMemberOwnerResolution::Missing => return,
639    };
640    let VisibleMemberResolution::Callable(callables) = ctx
641        .visibility
642        .visible_member_for_owner_name(ctx.file, &owner, name)
643    else {
644        return;
645    };
646    let mut candidates = callables.into_iter().filter(|candidate| {
647        cpp_callable_arity(&ctx.analyzer, candidate).is_none_or(|known| known.accepts(call.arity))
648            && ctx
649                .visibility
650                .callable_is_template_declaration(&ctx.analyzer, candidate)
651    });
652    let Some(candidate) = candidates.next() else {
653        return;
654    };
655    if candidates.next().is_some() {
656        ctx.record_unproven(name, call.member);
657        return;
658    }
659    ctx.record(candidate.fq_name(), call.member);
660}
661
662fn record_recovered_macro_return_type_reference(return_type: Node<'_>, ctx: &mut CppScan<'_>) {
663    let name = node_text(return_type, ctx.source);
664    let Some(scope) = recovered_or_indexed_lexical_scope(return_type, ctx) else {
665        ctx.record_unproven(name, return_type);
666        return;
667    };
668    let components = [name.to_string()];
669    if let LexicalTypeResolution::Resolved { unit, .. } = ctx
670        .visibility
671        .resolve_type_components_lexically(&ctx.analyzer, ctx.file, &components, false, &scope)
672    {
673        ctx.record(unit.fq_name(), return_type);
674        return;
675    }
676
677    let mut aliases = ctx
678        .visibility
679        .visible_identifier_candidates(ctx.file, name)
680        .filter(|candidate| {
681            ctx.analyzer
682                .type_alias_provider()
683                .is_some_and(|provider| provider.is_type_alias(candidate))
684                && ctx.visibility.external_type_candidate_visible_in_context(
685                    &ctx.analyzer,
686                    ctx.file,
687                    candidate,
688                    return_type,
689                )
690        })
691        .filter_map(|candidate| {
692            let owner = ctx.analyzer.parent_of(candidate)?;
693            let owner_components = canonical_cpp_scope_components(&owner);
694            scope
695                .starts_with(&owner_components)
696                .then_some((candidate, owner_components.len()))
697        })
698        .collect::<Vec<_>>();
699    let deepest = aliases.iter().map(|(_, depth)| *depth).max();
700    aliases.retain(|(_, depth)| Some(*depth) == deepest);
701    if aliases.len() == 1 {
702        ctx.record(aliases[0].0.fq_name(), return_type);
703    } else {
704        ctx.record_unproven(name, return_type);
705    }
706}
707
708/// A type node that is the direct type payload of a template argument.  The
709/// outer template-id is recorded separately, but these leaves can name class
710/// aliases (for example `expected<T, error_type>`) and therefore need their
711/// own inverse edge as well.
712fn record_type_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
713    let ordinary_resolution = resolve_type_node_lexically(
714        node,
715        &ctx.analyzer,
716        ctx.visibility,
717        &ctx.ordinary_type_imports,
718        ctx.file,
719        ctx.source,
720    );
721    let resolution = match recovered_sentinel_type_resolution(node, ctx) {
722        Some(recovered @ LexicalTypeResolution::Resolved { .. }) => recovered,
723        Some(LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing) | None => {
724            ordinary_resolution
725        }
726    };
727    let resolution = resolve_inverted_type_node(node, ctx, resolution);
728    match resolution {
729        LexicalTypeResolution::Resolved { unit, .. } => {
730            let callee = unit.fq_name();
731            let hit = type_reference_hit_node(node);
732            ctx.record(callee.clone(), hit);
733            if initialized_type_declaration_with_cast(node) {
734                ctx.record(callee.clone(), function_terminal_node(hit));
735            }
736            if qualified_alias_reference_requires_terminal(
737                qualified_alias_reference_preserves_target(
738                    node,
739                    &unit,
740                    &ctx.analyzer,
741                    ctx.visibility,
742                    ctx.file,
743                    ctx.source,
744                ),
745            ) {
746                let terminal = function_terminal_node(node);
747                if terminal.start_byte() != node.start_byte()
748                    || terminal.end_byte() != node.end_byte()
749                {
750                    ctx.record(callee, terminal);
751                }
752            }
753        }
754        LexicalTypeResolution::Missing => {
755            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
756                &ctx.analyzer,
757                ctx.file,
758                node,
759                ctx.source,
760            ) {
761                ctx.record(unit.fq_name(), node);
762            }
763        }
764        LexicalTypeResolution::Ambiguous => {}
765    }
766}
767
768fn record_nested_type_terminal_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
769    let Some(qualified) = node.parent().filter(|parent| {
770        matches!(
771            parent.kind(),
772            "qualified_identifier" | "scoped_type_identifier"
773        ) && parent.child_by_field_name("name") == Some(node)
774    }) else {
775        return;
776    };
777    let Some(owner) = qualified_owner_components(qualified, ctx.source) else {
778        return;
779    };
780    let mut complete = qualified;
781    while let Some(parent) = complete.parent().filter(|parent| {
782        matches!(
783            parent.kind(),
784            "qualified_identifier" | "scoped_type_identifier"
785        )
786    }) {
787        complete = parent;
788    }
789    if matches!(
790        resolve_type_node_lexically(
791            complete,
792            &ctx.analyzer,
793            ctx.visibility,
794            &ctx.ordinary_type_imports,
795            ctx.file,
796            ctx.source,
797        ),
798        LexicalTypeResolution::Resolved { .. }
799    ) {
800        return;
801    }
802
803    let Some(enclosing_owner) = enclosing_callable_owner(node, ctx) else {
804        return;
805    };
806    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
807    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
808        &ctx.analyzer,
809        ctx.file,
810        &owner.names,
811        owner.global,
812        &lexical_scope,
813    );
814    let owner_unit = match owner_resolution {
815        LexicalTypeResolution::Resolved { unit, .. } => unit,
816        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
817            let Some(unit) = ctx.visibility.inherited_injected_class_owner(
818                &ctx.analyzer,
819                ctx.file,
820                &enclosing_owner,
821                &owner.names[0],
822            ) else {
823                return;
824            };
825            unit
826        }
827        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return,
828    };
829    let name = node_text(node, ctx.source);
830    let mut candidates = Vec::new();
831    for candidate in ctx
832        .visibility
833        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
834        .into_iter()
835        .filter(|candidate| {
836            candidate.is_class()
837                || ctx
838                    .analyzer
839                    .type_alias_provider()
840                    .is_some_and(|provider| provider.is_type_alias(candidate))
841        })
842        .filter(|candidate| {
843            ctx.visibility.external_type_candidate_visible_in_context(
844                &ctx.analyzer,
845                ctx.file,
846                candidate,
847                node,
848            )
849        })
850    {
851        if !candidates
852            .iter()
853            .any(|existing| same_logical_symbol(existing, candidate))
854        {
855            candidates.push(candidate.clone());
856        }
857    }
858    if let [candidate] = candidates.as_slice() {
859        ctx.record(candidate.fq_name(), complete);
860    }
861}
862
863fn recovered_sentinel_type_resolution(
864    node: Node<'_>,
865    ctx: &CppScan<'_>,
866) -> Option<LexicalTypeResolution> {
867    let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
868    let components = cpp_type_name_components(node, ctx.source)?;
869    let global = is_globally_qualified_cpp_name(node);
870    Some(ctx.visibility.resolve_type_components_lexically(
871        &ctx.analyzer,
872        ctx.file,
873        &components,
874        global,
875        &scope,
876    ))
877}
878
879/// Forward and inverted C++ scans must agree on the owner scope when
880/// tree-sitter's malformed wrapper hides the structural class/namespace. The
881/// sentinel recovery is the most precise signal; otherwise use the shared
882/// lexical-scope reconstruction, which falls back to the indexed enclosing
883/// code-unit scope for displaced definitions.
884fn recovered_or_indexed_lexical_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<Vec<String>> {
885    ctx.recovered_sentinel_scope(node).or_else(|| {
886        match enclosing_lexical_scope_components(
887            node,
888            &ctx.analyzer,
889            ctx.visibility,
890            ctx.file,
891            ctx.source,
892        ) {
893            LexicalScopeResolution::Resolved(scope) => Some(scope),
894            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
895        }
896    })
897}
898
899/// Tree-sitter can place either side of a missing `::` in a recovered
900/// declaration's qualified scope: a prefix macro may leave the real type in
901/// the scope, while a suffix attribute can leave the macro there instead.
902/// Resolve both structured candidates. Recovery is usable only when exactly
903/// one candidate resolves; even two spellings that happen to resolve to the
904/// same logical symbol are ambiguous because one may be a macro token.
905fn record_recovered_macro_decorated_type_reference(
906    scope_node: Node<'_>,
907    type_node: Node<'_>,
908    ctx: &mut CppScan<'_>,
909) {
910    let mut resolved = Vec::new();
911    for candidate in [scope_node, type_node] {
912        if resolved
913            .iter()
914            .any(|(_, existing): &(CodeUnit, Node<'_>)| same_node(*existing, candidate))
915        {
916            continue;
917        }
918        if let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
919            candidate,
920            &ctx.analyzer,
921            ctx.visibility,
922            &ctx.ordinary_type_imports,
923            ctx.file,
924            ctx.source,
925        ) {
926            resolved.push((unit, candidate));
927        }
928    }
929    let [(unit, candidate)] = resolved.as_slice() else {
930        return;
931    };
932    ctx.record(unit.fq_name(), type_reference_hit_node(*candidate));
933}
934
935/// Resolve an inverted type edge to the concrete specialization named by a
936/// template-id.  The lexical resolver intentionally resolves the primary
937/// declaration: callers that need to preserve a concrete specialization (the
938/// reference graph included) must apply the parsed arguments afterwards.  If
939/// the primary cannot be specialized, retain the lexical result; that keeps
940/// dependent or incomplete template uses conservative rather than dropping a
941/// proven primary edge.
942fn resolve_inverted_type_node(
943    node: Node<'_>,
944    ctx: &CppScan<'_>,
945    resolution: LexicalTypeResolution,
946) -> LexicalTypeResolution {
947    let Some(arguments) = cpp_template_reference_arguments(node, ctx.source) else {
948        return resolution;
949    };
950    let LexicalTypeResolution::Resolved {
951        unit,
952        components,
953        candidates,
954    } = resolution
955    else {
956        return resolution;
957    };
958
959    let mut specialized = Vec::new();
960    for candidate in candidates.iter().chain(std::iter::once(&unit)) {
961        if let Ok(resolved) =
962            ctx.visibility
963                .resolve_template_arguments(ctx.file, candidate.clone(), &arguments)
964            && !specialized
965                .iter()
966                .any(|existing: &CodeUnit| same_visible_symbol(existing, &resolved))
967        {
968            specialized.push(resolved);
969        }
970    }
971    match specialized.as_slice() {
972        [specialized] => LexicalTypeResolution::Resolved {
973            unit: specialized.clone(),
974            components,
975            candidates,
976        },
977        _ => LexicalTypeResolution::Resolved {
978            unit,
979            components,
980            candidates,
981        },
982    }
983}
984
985fn record_qualified_callable_value(
986    qualified: Node<'_>,
987    global: bool,
988    owner_components: &[Node<'_>],
989    member_node: Node<'_>,
990    ctx: &mut CppScan<'_>,
991) {
992    let member_name = node_text(member_node, ctx.source);
993    if member_name.is_empty() {
994        return;
995    }
996    let owner_components = owner_components
997        .iter()
998        .map(|component| node_text(*component, ctx.source))
999        .map(str::to_string)
1000        .collect::<Vec<_>>();
1001    let lexical_scope = if global {
1002        Vec::new()
1003    } else {
1004        match enclosing_lexical_scope_components(
1005            qualified,
1006            &ctx.analyzer,
1007            ctx.visibility,
1008            ctx.file,
1009            ctx.source,
1010        ) {
1011            LexicalScopeResolution::Resolved(scope) => scope,
1012            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
1013                ctx.record_unproven(member_name, member_node);
1014                return;
1015            }
1016        }
1017    };
1018    let owner = match ctx.visibility.resolve_callable_value_components_lexically(
1019        &ctx.analyzer,
1020        ctx.file,
1021        &owner_components,
1022        member_name,
1023        global,
1024        &lexical_scope,
1025    ) {
1026        LexicalCallableValueResolution::Type(owner) => owner,
1027        LexicalCallableValueResolution::FreeFunction(function) => {
1028            ctx.record(function.fq_name(), member_node);
1029            return;
1030        }
1031        LexicalCallableValueResolution::Ambiguous => {
1032            ctx.record_unproven(member_name, member_node);
1033            return;
1034        }
1035        LexicalCallableValueResolution::Missing => match resolve_type_components_lexically_at(
1036            qualified,
1037            &owner_components,
1038            global,
1039            &ctx.analyzer,
1040            ctx.visibility,
1041            &ctx.ordinary_type_imports,
1042            ctx.file,
1043            ctx.source,
1044        ) {
1045            LexicalTypeResolution::Resolved { unit, .. } => unit,
1046            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
1047                ctx.record_unproven(member_name, member_node);
1048                return;
1049            }
1050        },
1051    };
1052    match ctx
1053        .visibility
1054        .visible_member_for_owner_name(ctx.file, &owner, member_name)
1055    {
1056        VisibleMemberResolution::Callable(callables) => {
1057            if let Some(callable) = callables.first() {
1058                ctx.record(callable.fq_name(), member_node);
1059            }
1060        }
1061        // Fields are intentionally absent from the workspace usage-graph node
1062        // catalog. A proven non-callable member is therefore a negative for this
1063        // callable edge pass, not an unresolved terminal-name fanout.
1064        VisibleMemberResolution::NonCallable => {}
1065        VisibleMemberResolution::AmbiguousKind | VisibleMemberResolution::Missing => {
1066            ctx.record_unproven(member_name, member_node);
1067        }
1068    }
1069}
1070
1071fn record_call(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &LocalInferenceEngine<CodeUnit>) {
1072    let Some(function) = node.child_by_field_name("function") else {
1073        return;
1074    };
1075    let Some(call_arity) = ctx
1076        .visibility
1077        .call_arity_evidence(ctx.file, node, ctx.source)
1078        .exact()
1079    else {
1080        let name_node = function
1081            .child_by_field_name("field")
1082            .or_else(|| function.child_by_field_name("name"))
1083            .unwrap_or(function);
1084        let name = node_text(name_node, ctx.source);
1085        if !name.is_empty() {
1086            ctx.record_unproven(name, name_node);
1087        }
1088        return;
1089    };
1090    match function.kind() {
1091        // `obj.m()` / `ptr->m()`: type the receiver, emit `Owner.m`.
1092        "field_expression" => {
1093            let Some(field) = function.child_by_field_name("field") else {
1094                return;
1095            };
1096            let name = node_text(field, ctx.source);
1097            if name.is_empty() {
1098                return;
1099            }
1100            let Some(receiver) = function
1101                .child_by_field_name("argument")
1102                .or_else(|| function.named_child(0))
1103            else {
1104                return;
1105            };
1106            if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
1107                // `this->m()` / `(*this).m()` is a same-owner call (#1138):
1108                // record it as unproven inbound rather than dropping it, so a
1109                // member reachable only through same-owner calls reads
1110                // INCONCLUSIVE, never confidently dead — uniformly with the
1111                // other languages.
1112                ctx.record_unproven(name, field);
1113                return;
1114            }
1115            if let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) {
1116                let ordinary = resolve_declaring_member_owner_cached(ctx, &receiver_owner, name);
1117                match resolve_declaring_callable_owner(
1118                    &ctx.analyzer,
1119                    ctx.visibility,
1120                    ctx.file,
1121                    ordinary,
1122                    name,
1123                    call_arity,
1124                ) {
1125                    EnclosingMemberOwnerResolution::Owner(owner) => {
1126                        match ctx
1127                            .visibility
1128                            .visible_member_for_owner_name(ctx.file, &owner, name)
1129                        {
1130                            VisibleMemberResolution::Callable(callables) => {
1131                                if let Some(callable) = callables.iter().find(|callable| {
1132                                    cpp_callable_arity(&ctx.analyzer, callable)
1133                                        .is_none_or(|known| known.accepts(call_arity))
1134                                }) {
1135                                    ctx.record(callable.fq_name(), field);
1136                                }
1137                            }
1138                            VisibleMemberResolution::AmbiguousKind => {
1139                                ctx.record_unproven(name, field);
1140                            }
1141                            VisibleMemberResolution::NonCallable
1142                            | VisibleMemberResolution::Missing => {}
1143                        }
1144                    }
1145                    EnclosingMemberOwnerResolution::Ambiguous => {
1146                        ctx.record_unproven(name, field);
1147                    }
1148                    EnclosingMemberOwnerResolution::Missing => {}
1149                }
1150            } else {
1151                ctx.record_unproven(name, field);
1152            }
1153        }
1154        // A bare `m(..)` is either a free function or an unqualified member call on
1155        // the enclosing class (`this`). `qualified_identifier` (`X::m`) is handled
1156        // by the type-reference case above.
1157        "identifier" | "template_function" => {
1158            let terminal = super::resolver::function_terminal_node(function);
1159            let name = node_text(terminal, ctx.source);
1160            if name.is_empty() {
1161                return;
1162            }
1163            if bindings.is_shadowed(name) {
1164                return;
1165            }
1166            if let Some(enclosing_owner) = enclosing_callable_owner(function, ctx) {
1167                let ordinary = resolve_declaring_member_owner_cached(ctx, &enclosing_owner, name);
1168                match resolve_declaring_callable_owner(
1169                    &ctx.analyzer,
1170                    ctx.visibility,
1171                    ctx.file,
1172                    ordinary,
1173                    name,
1174                    call_arity,
1175                ) {
1176                    EnclosingMemberOwnerResolution::Owner(owner)
1177                        if !same_visible_symbol(&owner, &enclosing_owner) =>
1178                    {
1179                        match ctx
1180                            .visibility
1181                            .visible_member_for_owner_name(ctx.file, &owner, name)
1182                        {
1183                            VisibleMemberResolution::Callable(callables) => {
1184                                if let Some(callable) = callables.iter().find(|callable| {
1185                                    cpp_callable_arity(&ctx.analyzer, callable)
1186                                        .is_none_or(|known| known.accepts(call_arity))
1187                                }) {
1188                                    ctx.record(callable.fq_name(), function);
1189                                }
1190                            }
1191                            VisibleMemberResolution::AmbiguousKind => {
1192                                ctx.record_unproven(name, function);
1193                            }
1194                            VisibleMemberResolution::NonCallable
1195                            | VisibleMemberResolution::Missing => {}
1196                        }
1197                        return;
1198                    }
1199                    EnclosingMemberOwnerResolution::Owner(_) => {
1200                        // Bare `m(..)` resolving to a method whose owner IS the
1201                        // enclosing class is a same-owner call (#1161, mirroring
1202                        // the `this->m()` fix at #1138): record it as unproven
1203                        // inbound rather than dropping it, so a member reachable
1204                        // only through bare implicit-this calls reads
1205                        // INCONCLUSIVE, never confidently dead — uniformly with
1206                        // the other languages and with the explicit-`this->m()`
1207                        // site above.
1208                        ctx.record_unproven(name, function);
1209                        return;
1210                    }
1211                    EnclosingMemberOwnerResolution::Ambiguous => {
1212                        ctx.record_unproven(name, function);
1213                        return;
1214                    }
1215                    EnclosingMemberOwnerResolution::Missing => {}
1216                }
1217            }
1218            let resolution = resolve_bare_call_target(
1219                node,
1220                function,
1221                &ctx.analyzer,
1222                ctx.visibility,
1223                &ctx.ordinary_type_imports,
1224                ctx.file,
1225                ctx.source,
1226            );
1227            match resolution {
1228                BareCallTargetResolution::FreeFunctions(units) => {
1229                    let mut recorded = HashSet::default();
1230                    for unit in units {
1231                        let fq_name = unit.fq_name();
1232                        if recorded.insert(fq_name.clone()) {
1233                            ctx.record(fq_name, terminal);
1234                        }
1235                    }
1236                }
1237                BareCallTargetResolution::Type(unit) => {
1238                    if let VisibleMemberResolution::Callable(constructors) = ctx
1239                        .visibility
1240                        .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
1241                        && let Some(constructor) = constructors.iter().find(|constructor| {
1242                            cpp_callable_arity(&ctx.analyzer, constructor)
1243                                .is_none_or(|known| known.accepts(call_arity))
1244                        })
1245                    {
1246                        ctx.record(constructor.fq_name(), terminal);
1247                    } else {
1248                        ctx.record(unit.fq_name(), function);
1249                    }
1250                }
1251                BareCallTargetResolution::UnprovenFreeFunctions(_)
1252                | BareCallTargetResolution::CallableShadow
1253                | BareCallTargetResolution::Ambiguous => {}
1254                BareCallTargetResolution::Missing => {}
1255            }
1256            // Direct/self member calls are intentionally omitted above; unique inherited
1257            // callable owners are recorded, while an unresolved bare name adds no edge.
1258        }
1259        _ => {}
1260    }
1261}
1262
1263fn resolve_declaring_member_owner_cached(
1264    ctx: &mut CppScan<'_>,
1265    receiver_owner: &CodeUnit,
1266    name: &str,
1267) -> EnclosingMemberOwnerResolution {
1268    if let Some(cached) = ctx
1269        .declaring_member_cache
1270        .get(receiver_owner)
1271        .and_then(|by_name| by_name.get(name))
1272        .cloned()
1273    {
1274        return cached;
1275    }
1276    let resolution = resolve_declaring_member_owner(
1277        &ctx.analyzer,
1278        ctx.visibility,
1279        ctx.file,
1280        receiver_owner,
1281        name,
1282    );
1283    ctx.declaring_member_cache
1284        .entry(receiver_owner.clone())
1285        .or_default()
1286        .insert(name.to_string(), resolution.clone());
1287    resolution
1288}
1289
1290fn enclosing_callable_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1291    let mut current = node.parent();
1292    while let Some(parent) = current {
1293        if parent.kind() == "function_definition" {
1294            let declarator = parent.child_by_field_name("declarator")?;
1295            let function = declarator_name_node(declarator)?;
1296            if let Some(owners) = out_of_line_member_definition_owner(
1297                &ctx.analyzer,
1298                ctx.visibility,
1299                ctx.file,
1300                ctx.source,
1301                function,
1302            ) && let Some((_, owner)) = owners.innermost()
1303            {
1304                return Some(owner.clone());
1305            }
1306            break;
1307        }
1308        current = parent.parent();
1309    }
1310    ctx.enclosing_class(node.start_byte()).and_then(|fqn| {
1311        ctx.analyzer
1312            .definitions(fqn)
1313            .find(|candidate| candidate.is_class())
1314    })
1315}
1316
1317/// Whether `receiver` is the implicit-object receiver of a member call.
1318///
1319/// `reference_is_c` is the compilation language the reference is read in
1320/// (#1970): `this` is the C++ implicit object only where C++ compiles the
1321/// source, and is an ordinary identifier in a `.c` file or a header every
1322/// reaching translation unit compiles as C.
1323fn receiver_is_self_like(receiver: Node<'_>, reference_is_c: bool) -> bool {
1324    match receiver.kind() {
1325        "this" => !reference_is_c,
1326        "parenthesized_expression" | "pointer_expression" => receiver
1327            .child_by_field_name("argument")
1328            .or_else(|| receiver.named_child(0))
1329            .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
1330        _ => false,
1331    }
1332}
1333
1334/// If `node` is the `function` of a namespace-qualified free-function call, its target.
1335fn scoped_free_function(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1336    if node.kind() != "qualified_identifier" {
1337        return None;
1338    }
1339    let parent = node.parent()?;
1340    if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1341        return None;
1342    }
1343    ctx.visibility.resolve_named(
1344        ctx.file,
1345        node_text(node, ctx.source),
1346        TargetKind::FreeFunction,
1347    )
1348}
1349
1350/// If `node` is the `function` of a `X::m(..)` call, the fqn of `X`'s type.
1351fn scoped_call_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<String> {
1352    if node.kind() != "qualified_identifier" {
1353        return None;
1354    }
1355    let parent = node.parent()?;
1356    if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1357        return None;
1358    }
1359    let scope = node.child_by_field_name("scope")?;
1360    match resolve_type_node_lexically(
1361        scope,
1362        &ctx.analyzer,
1363        ctx.visibility,
1364        &ctx.ordinary_type_imports,
1365        ctx.file,
1366        ctx.source,
1367    ) {
1368        LexicalTypeResolution::Resolved { unit, .. } => Some(unit.fq_name()),
1369        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1370    }
1371}
1372
1373/// The trailing member name of a `X::m` qualified identifier.
1374fn scoped_call_member(node: Node<'_>, source: &str) -> String {
1375    node.child_by_field_name("name")
1376        .map(|name| node_text(name, source).to_string())
1377        .unwrap_or_default()
1378}
1379
1380fn receiver_type_unit(
1381    receiver: Node<'_>,
1382    ctx: &CppScan<'_>,
1383    bindings: &LocalInferenceEngine<CodeUnit>,
1384    remaining_call_depth: usize,
1385) -> Option<CodeUnit> {
1386    match receiver.kind() {
1387        "identifier" => {
1388            let name = node_text(receiver, ctx.source);
1389            // A function-like macro's replacement local shadows bindings from
1390            // the caller at the source-backed replacement range. Resolve that
1391            // structured declaration before ordinary local inference, and
1392            // keep an unresolved macro type shadowed rather than treating the
1393            // spelling as a static type.
1394            if let Some(binding) = ctx.visibility.macro_local_binding_at(
1395                ctx.file,
1396                ctx.input.root(),
1397                ctx.source,
1398                receiver.start_byte(),
1399                receiver.end_byte(),
1400            ) && binding.name == name
1401            {
1402                return binding
1403                    .proven_unit
1404                    .or_else(|| {
1405                        binding.type_node.and_then(|type_node| {
1406                            resolve_type_node_with_recovered_scope(type_node, ctx)
1407                        })
1408                    })
1409                    .or_else(|| {
1410                        binding.type_node.and_then(|type_node| {
1411                            ctx.resolve_type_node_result(type_node).ok().flatten()
1412                        })
1413                    })
1414                    .or_else(|| ctx.resolve_type(&normalize_type_text(&binding.type_name)));
1415            }
1416            // A typed local resolves to its type; otherwise the name may itself be a
1417            // type, unless it is a known (shadowed) untyped local — never reinterpret
1418            // a value as a static type.
1419            first_precise(bindings, name).or_else(|| {
1420                if !bindings.is_shadowed(name)
1421                    && let Some(unit) = global_receiver_type_unit(ctx, name)
1422                {
1423                    return Some(unit);
1424                }
1425                (!bindings.is_shadowed(name))
1426                    .then(|| resolve_type_node_with_recovered_scope(receiver, ctx))
1427                    .flatten()
1428                    .or_else(|| {
1429                        (!bindings.is_shadowed(name))
1430                            .then(|| ctx.resolve_type(name))
1431                            .flatten()
1432                    })
1433            })
1434        }
1435        "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
1436            first_precise(bindings, node_text(receiver, ctx.source))
1437        }
1438        "this" => ctx.enclosing_class(receiver.start_byte()).and_then(|fqn| {
1439            ctx.analyzer
1440                .definitions(fqn)
1441                .find(|candidate| candidate.is_class())
1442        }),
1443        // `(*p).m()` / `(p).m()` unwrap to the inner receiver.
1444        "parenthesized_expression" | "pointer_expression" => receiver
1445            .child_by_field_name("argument")
1446            .or_else(|| receiver.named_child(0))
1447            .and_then(|inner| receiver_type_unit(inner, ctx, bindings, remaining_call_depth)),
1448        "call_expression" if remaining_call_depth > 0 => infer_cpp_initializer_binding(
1449            &ctx.analyzer,
1450            ctx.visibility,
1451            ctx.file,
1452            ctx.source,
1453            receiver,
1454            Some(&|inner, _source| {
1455                receiver_type_unit(inner, ctx, bindings, remaining_call_depth - 1)
1456                    .into_iter()
1457                    .collect()
1458            }),
1459        )
1460        .and_then(|binding| binding.unit),
1461        // A nested C selector carries the declared type of its selected
1462        // field. Resolve that field through the same owner/hierarchy lookup
1463        // used by ordinary field references; this preserves synthetic owners
1464        // for named anonymous aggregates without parsing type text.
1465        "field_expression" => {
1466            let field = receiver.child_by_field_name("field")?;
1467            let name = node_text(field, ctx.source);
1468            let inner = cpp_field_expression_receiver(receiver)?;
1469            let receiver_owner = receiver_type_unit(inner, ctx, bindings, remaining_call_depth)?;
1470            let owner = match resolve_declaring_member_owner(
1471                &ctx.analyzer,
1472                ctx.visibility,
1473                ctx.file,
1474                &receiver_owner,
1475                name,
1476            ) {
1477                EnclosingMemberOwnerResolution::Owner(owner) => owner,
1478                EnclosingMemberOwnerResolution::Ambiguous
1479                | EnclosingMemberOwnerResolution::Missing => return None,
1480            };
1481            let fields = ctx
1482                .visibility
1483                .visible_members_for_owner_name(ctx.file, &owner, name)
1484                .into_iter()
1485                .filter(|candidate| candidate.is_field())
1486                .collect::<Vec<_>>();
1487            let [field_unit] = fields.as_slice() else {
1488                return None;
1489            };
1490            field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field_unit)
1491                .and_then(|binding| binding.unit)
1492        }
1493        "cast_expression" => cast_expression_type_node(receiver).and_then(|type_node| {
1494            resolve_type_node_with_recovered_scope(type_node, ctx).or_else(|| {
1495                match ctx.resolve_type_node_result(type_node) {
1496                    Ok(Some(unit)) => Some(unit),
1497                    Ok(None) => ctx.resolve_type(node_text(type_node, ctx.source)),
1498                    Err(_) => None,
1499                }
1500            })
1501        }),
1502        _ => None,
1503    }
1504}
1505
1506fn global_receiver_type_unit(ctx: &CppScan<'_>, name: &str) -> Option<CodeUnit> {
1507    let fields = ctx
1508        .visibility
1509        .named_candidates(ctx.file, name, TargetKind::GlobalField)
1510        .into_iter()
1511        .filter(has_persisted_global_field_identity)
1512        .collect::<Vec<_>>();
1513    let first = fields.first()?;
1514    let mut linkage_cache = HashMap::default();
1515    if fields.iter().skip(1).any(|field| {
1516        !same_visible_global_field_symbol(&ctx.analyzer, &mut linkage_cache, first, field)
1517    }) {
1518        return None;
1519    }
1520    let mut units = fields
1521        .into_iter()
1522        .filter_map(|field| {
1523            field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, &field)
1524                .and_then(|binding| binding.unit)
1525        })
1526        .collect::<Vec<_>>();
1527    units.dedup_by(|left, right| same_visible_symbol(left, right));
1528    match units.as_slice() {
1529        [unit] => Some(unit.clone()),
1530        _ => None,
1531    }
1532}
1533
1534fn seed_declaration(
1535    node: Node<'_>,
1536    ctx: &mut CppScan<'_>,
1537    bindings: &mut LocalInferenceEngine<CodeUnit>,
1538) {
1539    if recovered_macro_return_type_node(node, ctx.source).is_some()
1540        || crate::declarations::is_direct_recovered_exported_class_field_declaration(
1541            node, ctx.source,
1542        )
1543    {
1544        return;
1545    }
1546    match node.kind() {
1547        "parameter_declaration" | "optional_parameter_declaration" => {
1548            seed_typed_binding(node, ctx, bindings)
1549        }
1550        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx, bindings),
1551        "for_range_loop" => seed_range_binding(node, ctx, bindings),
1552        "expression_statement" => seed_function_macro_local_binding(node, ctx, bindings),
1553        "assignment_expression" => seed_function_macro_container_binding(node, ctx, bindings),
1554        _ => {}
1555    }
1556}
1557
1558fn seed_function_macro_local_binding(
1559    node: Node<'_>,
1560    ctx: &CppScan<'_>,
1561    bindings: &mut LocalInferenceEngine<CodeUnit>,
1562) {
1563    let Some(binding) = ctx
1564        .visibility
1565        .function_macro_local_binding(ctx.file, node, ctx.source)
1566    else {
1567        return;
1568    };
1569    let unit = binding
1570        .type_node
1571        .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1572        .or_else(|| {
1573            binding
1574                .type_node
1575                .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1576        })
1577        .or_else(|| ctx.resolve_type(&binding.type_name));
1578    match unit {
1579        Some(unit) => bindings.seed_symbol(binding.name, unit),
1580        None => bindings.declare_shadow(binding.name),
1581    }
1582}
1583
1584fn seed_function_macro_container_binding(
1585    node: Node<'_>,
1586    ctx: &CppScan<'_>,
1587    bindings: &mut LocalInferenceEngine<CodeUnit>,
1588) {
1589    let Some(binding) =
1590        ctx.visibility
1591            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
1592    else {
1593        return;
1594    };
1595    let unit = binding.proven_unit.or_else(|| {
1596        binding
1597            .type_node
1598            .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1599            .or_else(|| {
1600                binding
1601                    .type_node
1602                    .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1603            })
1604            .or_else(|| ctx.resolve_type(&binding.type_name))
1605    });
1606    match unit {
1607        Some(unit) => bindings.seed_symbol(binding.name, unit),
1608        None => bindings.declare_shadow(binding.name),
1609    }
1610}
1611
1612fn seed_typed_binding(
1613    node: Node<'_>,
1614    ctx: &CppScan<'_>,
1615    bindings: &mut LocalInferenceEngine<CodeUnit>,
1616) {
1617    if !parameter_belongs_to_callable_scope(node) {
1618        return;
1619    }
1620    let Some(declarator) = node.child_by_field_name("declarator") else {
1621        return;
1622    };
1623    let Some(name) = extract_variable_name(declarator, ctx.source) else {
1624        return;
1625    };
1626    let type_node = node
1627        .child_by_field_name("type")
1628        .or_else(|| first_type_child(node));
1629    seed_binding(&name, type_node, None, ctx, bindings);
1630}
1631
1632fn seed_range_binding(
1633    node: Node<'_>,
1634    ctx: &CppScan<'_>,
1635    bindings: &mut LocalInferenceEngine<CodeUnit>,
1636) {
1637    let Some(declarator) = node.child_by_field_name("declarator") else {
1638        return;
1639    };
1640    let Some(name) = extract_variable_name(declarator, ctx.source) else {
1641        return;
1642    };
1643    let type_node = node
1644        .child_by_field_name("type")
1645        .or_else(|| first_type_child(node));
1646    seed_binding(&name, type_node, None, ctx, bindings);
1647}
1648
1649fn seed_variable_declaration(
1650    node: Node<'_>,
1651    ctx: &CppScan<'_>,
1652    bindings: &mut LocalInferenceEngine<CodeUnit>,
1653) {
1654    let type_node = node
1655        .child_by_field_name("type")
1656        .or_else(|| first_type_child(node));
1657    let type_text =
1658        type_node.map(|type_node| normalize_type_text(node_text(type_node, ctx.source)));
1659    let mut cursor = node.walk();
1660    for child in node.named_children(&mut cursor) {
1661        let declarator = if child.kind() == "init_declarator" {
1662            child.child_by_field_name("declarator")
1663        } else if is_declarator_node(child) {
1664            Some(child)
1665        } else {
1666            None
1667        };
1668        let Some(declarator) = declarator else {
1669            continue;
1670        };
1671        if declarator.kind() == "function_declarator"
1672            && !constructor_style_local_declaration(
1673                ctx.visibility,
1674                ctx.file,
1675                ctx.source,
1676                declarator,
1677                type_text.as_deref(),
1678                bindings,
1679            )
1680        {
1681            if node.kind() == "declaration"
1682                && has_function_scope_ancestor(node)
1683                && let Some(name) = extract_variable_name(declarator, ctx.source)
1684            {
1685                bindings.declare_shadow(name);
1686            }
1687            continue;
1688        }
1689        let Some(name) = extract_variable_name(declarator, ctx.source) else {
1690            continue;
1691        };
1692        let value = child.child_by_field_name("value");
1693        seed_binding(&name, type_node, value, ctx, bindings);
1694    }
1695}
1696
1697fn has_function_scope_ancestor(mut node: Node<'_>) -> bool {
1698    while let Some(parent) = node.parent() {
1699        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1700            return true;
1701        }
1702        node = parent;
1703    }
1704    false
1705}
1706
1707fn seed_binding(
1708    name: &str,
1709    type_node: Option<Node<'_>>,
1710    value: Option<Node<'_>>,
1711    ctx: &CppScan<'_>,
1712    bindings: &mut LocalInferenceEngine<CodeUnit>,
1713) {
1714    if name.is_empty() {
1715        return;
1716    }
1717    // A declared type resolves directly; `auto x = new Foo()` infers from the
1718    // initializer. A declared-but-unresolved local is shadowed so a later
1719    // member access never falls back to static type resolution on its name.
1720    let declared_type =
1721        type_node.filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto");
1722    let resolved = match declared_type {
1723        Some(node) => resolve_type_node_with_recovered_scope(node, ctx).or_else(|| {
1724            match ctx.resolve_type_node_result(node) {
1725                Ok(Some(unit)) => Some(unit),
1726                Ok(None) => ctx.resolve_type(node_text(node, ctx.source)),
1727                Err(_) => None,
1728            }
1729        }),
1730        None => value.and_then(|value| infer_type_from_value(value, ctx)),
1731    };
1732    match resolved {
1733        Some(unit) => bindings.seed_symbol(name.to_string(), unit),
1734        None => bindings.declare_shadow(name.to_string()),
1735    }
1736}
1737
1738fn resolve_type_node_with_recovered_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1739    if let Some(owner) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
1740        return Some(owner);
1741    }
1742    let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
1743    let components = cpp_type_name_components(node, ctx.source)?;
1744    let global = is_globally_qualified_cpp_name(node);
1745    match ctx.visibility.resolve_type_components_lexically(
1746        &ctx.analyzer,
1747        ctx.file,
1748        &components,
1749        global,
1750        &scope,
1751    ) {
1752        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
1753        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1754    }
1755}
1756
1757/// Infer a class type from an initializer expression for `auto`/untyped locals.
1758fn infer_type_from_value(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1759    infer_cpp_initializer_type(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
1760}