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).accepts(call_arity)
471                    })
472                {
473                    ctx.record(constructor.fq_name(), node);
474                } else {
475                    ctx.record(unit.fq_name(), node);
476                }
477                return;
478            }
479            if let Some(owner) = scoped_call_owner(node, ctx) {
480                let member = scoped_call_member(node, ctx.source);
481                if !member.is_empty() {
482                    ctx.record(format!("{owner}.{member}"), function_terminal_node(node));
483                    return;
484                }
485            }
486            record_type_reference(node, ctx);
487        }
488        "call_expression" => record_call(node, ctx, bindings),
489        _ => {}
490    }
491}
492
493fn record_c_offsetof_field_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
494    let name = node_text(node, ctx.source);
495    let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
496        ctx.record_unproven(name, node);
497        return;
498    };
499    let owner = match resolve_type_node_lexically(
500        type_reference,
501        &ctx.analyzer,
502        ctx.visibility,
503        &ctx.ordinary_type_imports,
504        ctx.file,
505        ctx.source,
506    ) {
507        LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
508        LexicalTypeResolution::Resolved { .. }
509        | LexicalTypeResolution::Ambiguous
510        | LexicalTypeResolution::Missing => {
511            ctx.record_unproven(name, member);
512            return;
513        }
514    };
515    let candidates = ctx
516        .visibility
517        .visible_members_for_owner_name(ctx.file, &owner, name)
518        .into_iter()
519        .filter(|candidate| candidate.is_field())
520        .collect::<Vec<_>>();
521    if candidates.len() == 1 {
522        ctx.record(candidates[0].fq_name(), member);
523    } else if candidates.len() > 1 {
524        ctx.record_unproven(name, member);
525    }
526}
527fn record_c_field_reference(
528    node: Node<'_>,
529    ctx: &mut CppScan<'_>,
530    bindings: &LocalInferenceEngine<CodeUnit>,
531) {
532    let Some(field) = node.child_by_field_name("field") else {
533        return;
534    };
535    let name = node_text(field, ctx.source);
536    if name.is_empty() {
537        return;
538    }
539    let Some(receiver) = cpp_field_expression_receiver(node) else {
540        ctx.record_unproven(name, field);
541        return;
542    };
543    let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) else {
544        ctx.record_unproven(name, field);
545        return;
546    };
547    let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
548        EnclosingMemberOwnerResolution::Owner(owner) => owner,
549        EnclosingMemberOwnerResolution::Ambiguous => {
550            ctx.record_unproven(name, field);
551            return;
552        }
553        EnclosingMemberOwnerResolution::Missing => return,
554    };
555    let fields = ctx
556        .visibility
557        .visible_members_for_owner_name(ctx.file, &owner, name)
558        .into_iter()
559        .filter(|candidate| {
560            candidate.is_field()
561                && (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
562                    || ctx.visibility.declaration_visible_at_reference(
563                        &ctx.analyzer,
564                        ctx.file,
565                        candidate,
566                        field,
567                    ))
568        })
569        .collect::<Vec<_>>();
570    match fields.as_slice() {
571        [candidate] => ctx.record(candidate.fq_name(), field),
572        [] => {}
573        _ => {
574            if fields
575                .iter()
576                .skip(1)
577                .all(|candidate| same_visible_symbol(candidate, fields[0]))
578            {
579                ctx.record(fields[0].fq_name(), field);
580            } else {
581                ctx.record_unproven(name, field);
582            }
583        }
584    }
585}
586
587fn record_object_macro_replacement_type_references(node: Node<'_>, ctx: &mut CppScan<'_>) {
588    for reference in object_macro_replacement_type_references(node, ctx.source) {
589        for component_count in 1..=reference.components.len() {
590            let resolution = resolve_type_components_lexically_at(
591                node,
592                &reference.components[..component_count],
593                reference.global,
594                &ctx.analyzer,
595                ctx.visibility,
596                &ctx.ordinary_type_imports,
597                ctx.file,
598                ctx.source,
599            );
600            let unit = match resolution {
601                LexicalTypeResolution::Resolved { unit, .. } => unit,
602                LexicalTypeResolution::Missing => {
603                    let Some(unit) = unique_macro_replacement_type_candidate(
604                        &ctx.analyzer,
605                        ctx.visibility,
606                        ctx.file,
607                        &reference.components[..component_count],
608                    ) else {
609                        continue;
610                    };
611                    unit
612                }
613                LexicalTypeResolution::Ambiguous => continue,
614            };
615            let range = &reference.component_ranges[component_count - 1];
616            ctx.record_range(unit.fq_name(), node, range.start, range.end);
617        }
618    }
619}
620
621fn record_recovered_relational_template_member_call(
622    call: crate::graph::resolver::RecoveredRelationalTemplateMemberCall<'_>,
623    ctx: &mut CppScan<'_>,
624    bindings: &LocalInferenceEngine<CodeUnit>,
625) {
626    let name = node_text(call.member, ctx.source);
627    let Some(receiver_owner) = receiver_type_unit(call.receiver, ctx, bindings, 32) else {
628        ctx.record_unproven(name, call.member);
629        return;
630    };
631    let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
632        EnclosingMemberOwnerResolution::Owner(owner) => owner,
633        EnclosingMemberOwnerResolution::Ambiguous => {
634            ctx.record_unproven(name, call.member);
635            return;
636        }
637        EnclosingMemberOwnerResolution::Missing => return,
638    };
639    let VisibleMemberResolution::Callable(callables) = ctx
640        .visibility
641        .visible_member_for_owner_name(ctx.file, &owner, name)
642    else {
643        return;
644    };
645    let mut candidates = callables.into_iter().filter(|candidate| {
646        cpp_callable_arity(&ctx.analyzer, candidate).accepts(call.arity)
647            && ctx
648                .visibility
649                .callable_is_template_declaration(&ctx.analyzer, candidate)
650    });
651    let Some(candidate) = candidates.next() else {
652        return;
653    };
654    if candidates.next().is_some() {
655        ctx.record_unproven(name, call.member);
656        return;
657    }
658    ctx.record(candidate.fq_name(), call.member);
659}
660
661fn record_recovered_macro_return_type_reference(return_type: Node<'_>, ctx: &mut CppScan<'_>) {
662    let name = node_text(return_type, ctx.source);
663    let Some(scope) = recovered_or_indexed_lexical_scope(return_type, ctx) else {
664        ctx.record_unproven(name, return_type);
665        return;
666    };
667    let components = [name.to_string()];
668    if let LexicalTypeResolution::Resolved { unit, .. } = ctx
669        .visibility
670        .resolve_type_components_lexically(&ctx.analyzer, ctx.file, &components, false, &scope)
671    {
672        ctx.record(unit.fq_name(), return_type);
673        return;
674    }
675
676    let mut aliases = ctx
677        .visibility
678        .visible_identifier_candidates(ctx.file, name)
679        .filter(|candidate| {
680            ctx.analyzer
681                .type_alias_provider()
682                .is_some_and(|provider| provider.is_type_alias(candidate))
683                && ctx.visibility.external_type_candidate_visible_in_context(
684                    &ctx.analyzer,
685                    ctx.file,
686                    candidate,
687                    return_type,
688                )
689        })
690        .filter_map(|candidate| {
691            let owner = ctx.analyzer.parent_of(candidate)?;
692            let owner_components = canonical_cpp_scope_components(&owner);
693            scope
694                .starts_with(&owner_components)
695                .then_some((candidate, owner_components.len()))
696        })
697        .collect::<Vec<_>>();
698    let deepest = aliases.iter().map(|(_, depth)| *depth).max();
699    aliases.retain(|(_, depth)| Some(*depth) == deepest);
700    if aliases.len() == 1 {
701        ctx.record(aliases[0].0.fq_name(), return_type);
702    } else {
703        ctx.record_unproven(name, return_type);
704    }
705}
706
707/// A type node that is the direct type payload of a template argument.  The
708/// outer template-id is recorded separately, but these leaves can name class
709/// aliases (for example `expected<T, error_type>`) and therefore need their
710/// own inverse edge as well.
711fn record_type_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
712    let ordinary_resolution = resolve_type_node_lexically(
713        node,
714        &ctx.analyzer,
715        ctx.visibility,
716        &ctx.ordinary_type_imports,
717        ctx.file,
718        ctx.source,
719    );
720    let resolution = match recovered_sentinel_type_resolution(node, ctx) {
721        Some(recovered @ LexicalTypeResolution::Resolved { .. }) => recovered,
722        Some(LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing) | None => {
723            ordinary_resolution
724        }
725    };
726    let resolution = resolve_inverted_type_node(node, ctx, resolution);
727    match resolution {
728        LexicalTypeResolution::Resolved { unit, .. } => {
729            let callee = unit.fq_name();
730            let hit = type_reference_hit_node(node);
731            ctx.record(callee.clone(), hit);
732            if initialized_type_declaration_with_cast(node) {
733                ctx.record(callee.clone(), function_terminal_node(hit));
734            }
735            if qualified_alias_reference_requires_terminal(
736                qualified_alias_reference_preserves_target(
737                    node,
738                    &unit,
739                    &ctx.analyzer,
740                    ctx.visibility,
741                    ctx.file,
742                    ctx.source,
743                ),
744            ) {
745                let terminal = function_terminal_node(node);
746                if terminal.start_byte() != node.start_byte()
747                    || terminal.end_byte() != node.end_byte()
748                {
749                    ctx.record(callee, terminal);
750                }
751            }
752        }
753        LexicalTypeResolution::Missing => {
754            if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
755                &ctx.analyzer,
756                ctx.file,
757                node,
758                ctx.source,
759            ) {
760                ctx.record(unit.fq_name(), node);
761            }
762        }
763        LexicalTypeResolution::Ambiguous => {}
764    }
765}
766
767fn record_nested_type_terminal_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
768    let Some(qualified) = node.parent().filter(|parent| {
769        matches!(
770            parent.kind(),
771            "qualified_identifier" | "scoped_type_identifier"
772        ) && parent.child_by_field_name("name") == Some(node)
773    }) else {
774        return;
775    };
776    let Some(owner) = qualified_owner_components(qualified, ctx.source) else {
777        return;
778    };
779    let mut complete = qualified;
780    while let Some(parent) = complete.parent().filter(|parent| {
781        matches!(
782            parent.kind(),
783            "qualified_identifier" | "scoped_type_identifier"
784        )
785    }) {
786        complete = parent;
787    }
788    if matches!(
789        resolve_type_node_lexically(
790            complete,
791            &ctx.analyzer,
792            ctx.visibility,
793            &ctx.ordinary_type_imports,
794            ctx.file,
795            ctx.source,
796        ),
797        LexicalTypeResolution::Resolved { .. }
798    ) {
799        return;
800    }
801
802    let Some(enclosing_owner) = enclosing_callable_owner(node, ctx) else {
803        return;
804    };
805    let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
806    let owner_resolution = ctx.visibility.resolve_type_components_lexically(
807        &ctx.analyzer,
808        ctx.file,
809        &owner.names,
810        owner.global,
811        &lexical_scope,
812    );
813    let owner_unit = match owner_resolution {
814        LexicalTypeResolution::Resolved { unit, .. } => unit,
815        LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
816            let Some(unit) = ctx.visibility.inherited_injected_class_owner(
817                &ctx.analyzer,
818                ctx.file,
819                &enclosing_owner,
820                &owner.names[0],
821            ) else {
822                return;
823            };
824            unit
825        }
826        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return,
827    };
828    let name = node_text(node, ctx.source);
829    let mut candidates = Vec::new();
830    for candidate in ctx
831        .visibility
832        .visible_members_for_owner_name(ctx.file, &owner_unit, name)
833        .into_iter()
834        .filter(|candidate| {
835            candidate.is_class()
836                || ctx
837                    .analyzer
838                    .type_alias_provider()
839                    .is_some_and(|provider| provider.is_type_alias(candidate))
840        })
841        .filter(|candidate| {
842            ctx.visibility.external_type_candidate_visible_in_context(
843                &ctx.analyzer,
844                ctx.file,
845                candidate,
846                node,
847            )
848        })
849    {
850        if !candidates
851            .iter()
852            .any(|existing| same_logical_symbol(existing, candidate))
853        {
854            candidates.push(candidate.clone());
855        }
856    }
857    if let [candidate] = candidates.as_slice() {
858        ctx.record(candidate.fq_name(), complete);
859    }
860}
861
862fn recovered_sentinel_type_resolution(
863    node: Node<'_>,
864    ctx: &CppScan<'_>,
865) -> Option<LexicalTypeResolution> {
866    let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
867    let components = cpp_type_name_components(node, ctx.source)?;
868    let global = is_globally_qualified_cpp_name(node);
869    Some(ctx.visibility.resolve_type_components_lexically(
870        &ctx.analyzer,
871        ctx.file,
872        &components,
873        global,
874        &scope,
875    ))
876}
877
878/// Forward and inverted C++ scans must agree on the owner scope when
879/// tree-sitter's malformed wrapper hides the structural class/namespace. The
880/// sentinel recovery is the most precise signal; otherwise use the shared
881/// lexical-scope reconstruction, which falls back to the indexed enclosing
882/// code-unit scope for displaced definitions.
883fn recovered_or_indexed_lexical_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<Vec<String>> {
884    ctx.recovered_sentinel_scope(node).or_else(|| {
885        match enclosing_lexical_scope_components(
886            node,
887            &ctx.analyzer,
888            ctx.visibility,
889            ctx.file,
890            ctx.source,
891        ) {
892            LexicalScopeResolution::Resolved(scope) => Some(scope),
893            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
894        }
895    })
896}
897
898/// Tree-sitter can place either side of a missing `::` in a recovered
899/// declaration's qualified scope: a prefix macro may leave the real type in
900/// the scope, while a suffix attribute can leave the macro there instead.
901/// Resolve both structured candidates. Recovery is usable only when exactly
902/// one candidate resolves; even two spellings that happen to resolve to the
903/// same logical symbol are ambiguous because one may be a macro token.
904fn record_recovered_macro_decorated_type_reference(
905    scope_node: Node<'_>,
906    type_node: Node<'_>,
907    ctx: &mut CppScan<'_>,
908) {
909    let mut resolved = Vec::new();
910    for candidate in [scope_node, type_node] {
911        if resolved
912            .iter()
913            .any(|(_, existing): &(CodeUnit, Node<'_>)| same_node(*existing, candidate))
914        {
915            continue;
916        }
917        if let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
918            candidate,
919            &ctx.analyzer,
920            ctx.visibility,
921            &ctx.ordinary_type_imports,
922            ctx.file,
923            ctx.source,
924        ) {
925            resolved.push((unit, candidate));
926        }
927    }
928    let [(unit, candidate)] = resolved.as_slice() else {
929        return;
930    };
931    ctx.record(unit.fq_name(), type_reference_hit_node(*candidate));
932}
933
934/// Resolve an inverted type edge to the concrete specialization named by a
935/// template-id.  The lexical resolver intentionally resolves the primary
936/// declaration: callers that need to preserve a concrete specialization (the
937/// reference graph included) must apply the parsed arguments afterwards.  If
938/// the primary cannot be specialized, retain the lexical result; that keeps
939/// dependent or incomplete template uses conservative rather than dropping a
940/// proven primary edge.
941fn resolve_inverted_type_node(
942    node: Node<'_>,
943    ctx: &CppScan<'_>,
944    resolution: LexicalTypeResolution,
945) -> LexicalTypeResolution {
946    let Some(arguments) = cpp_template_reference_arguments(node, ctx.source) else {
947        return resolution;
948    };
949    let LexicalTypeResolution::Resolved {
950        unit,
951        components,
952        candidates,
953    } = resolution
954    else {
955        return resolution;
956    };
957
958    let mut specialized = Vec::new();
959    for candidate in candidates.iter().chain(std::iter::once(&unit)) {
960        if let Ok(resolved) =
961            ctx.visibility
962                .resolve_template_arguments(ctx.file, candidate.clone(), &arguments)
963            && !specialized
964                .iter()
965                .any(|existing: &CodeUnit| same_visible_symbol(existing, &resolved))
966        {
967            specialized.push(resolved);
968        }
969    }
970    match specialized.as_slice() {
971        [specialized] => LexicalTypeResolution::Resolved {
972            unit: specialized.clone(),
973            components,
974            candidates,
975        },
976        _ => LexicalTypeResolution::Resolved {
977            unit,
978            components,
979            candidates,
980        },
981    }
982}
983
984fn record_qualified_callable_value(
985    qualified: Node<'_>,
986    global: bool,
987    owner_components: &[Node<'_>],
988    member_node: Node<'_>,
989    ctx: &mut CppScan<'_>,
990) {
991    let member_name = node_text(member_node, ctx.source);
992    if member_name.is_empty() {
993        return;
994    }
995    let owner_components = owner_components
996        .iter()
997        .map(|component| node_text(*component, ctx.source))
998        .map(str::to_string)
999        .collect::<Vec<_>>();
1000    let lexical_scope = if global {
1001        Vec::new()
1002    } else {
1003        match enclosing_lexical_scope_components(
1004            qualified,
1005            &ctx.analyzer,
1006            ctx.visibility,
1007            ctx.file,
1008            ctx.source,
1009        ) {
1010            LexicalScopeResolution::Resolved(scope) => scope,
1011            LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
1012                ctx.record_unproven(member_name, member_node);
1013                return;
1014            }
1015        }
1016    };
1017    let owner = match ctx.visibility.resolve_callable_value_components_lexically(
1018        &ctx.analyzer,
1019        ctx.file,
1020        &owner_components,
1021        member_name,
1022        global,
1023        &lexical_scope,
1024    ) {
1025        LexicalCallableValueResolution::Type(owner) => owner,
1026        LexicalCallableValueResolution::FreeFunction(function) => {
1027            ctx.record(function.fq_name(), member_node);
1028            return;
1029        }
1030        LexicalCallableValueResolution::Ambiguous => {
1031            ctx.record_unproven(member_name, member_node);
1032            return;
1033        }
1034        LexicalCallableValueResolution::Missing => match resolve_type_components_lexically_at(
1035            qualified,
1036            &owner_components,
1037            global,
1038            &ctx.analyzer,
1039            ctx.visibility,
1040            &ctx.ordinary_type_imports,
1041            ctx.file,
1042            ctx.source,
1043        ) {
1044            LexicalTypeResolution::Resolved { unit, .. } => unit,
1045            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
1046                ctx.record_unproven(member_name, member_node);
1047                return;
1048            }
1049        },
1050    };
1051    match ctx
1052        .visibility
1053        .visible_member_for_owner_name(ctx.file, &owner, member_name)
1054    {
1055        VisibleMemberResolution::Callable(callables) => {
1056            if let Some(callable) = callables.first() {
1057                ctx.record(callable.fq_name(), member_node);
1058            }
1059        }
1060        // Fields are intentionally absent from the workspace usage-graph node
1061        // catalog. A proven non-callable member is therefore a negative for this
1062        // callable edge pass, not an unresolved terminal-name fanout.
1063        VisibleMemberResolution::NonCallable => {}
1064        VisibleMemberResolution::AmbiguousKind | VisibleMemberResolution::Missing => {
1065            ctx.record_unproven(member_name, member_node);
1066        }
1067    }
1068}
1069
1070fn record_call(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &LocalInferenceEngine<CodeUnit>) {
1071    let Some(function) = node.child_by_field_name("function") else {
1072        return;
1073    };
1074    let Some(call_arity) = ctx
1075        .visibility
1076        .call_arity_evidence(ctx.file, node, ctx.source)
1077        .exact()
1078    else {
1079        let name_node = function
1080            .child_by_field_name("field")
1081            .or_else(|| function.child_by_field_name("name"))
1082            .unwrap_or(function);
1083        let name = node_text(name_node, ctx.source);
1084        if !name.is_empty() {
1085            ctx.record_unproven(name, name_node);
1086        }
1087        return;
1088    };
1089    match function.kind() {
1090        // `obj.m()` / `ptr->m()`: type the receiver, emit `Owner.m`.
1091        "field_expression" => {
1092            let Some(field) = function.child_by_field_name("field") else {
1093                return;
1094            };
1095            let name = node_text(field, ctx.source);
1096            if name.is_empty() {
1097                return;
1098            }
1099            let Some(receiver) = function
1100                .child_by_field_name("argument")
1101                .or_else(|| function.named_child(0))
1102            else {
1103                return;
1104            };
1105            if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
1106                // `this->m()` / `(*this).m()` is a same-owner call (#1138):
1107                // record it as unproven inbound rather than dropping it, so a
1108                // member reachable only through same-owner calls reads
1109                // INCONCLUSIVE, never confidently dead — uniformly with the
1110                // other languages.
1111                ctx.record_unproven(name, field);
1112                return;
1113            }
1114            if let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) {
1115                let ordinary = resolve_declaring_member_owner_cached(ctx, &receiver_owner, name);
1116                match resolve_declaring_callable_owner(
1117                    &ctx.analyzer,
1118                    ctx.visibility,
1119                    ctx.file,
1120                    ordinary,
1121                    name,
1122                    call_arity,
1123                ) {
1124                    EnclosingMemberOwnerResolution::Owner(owner) => {
1125                        match ctx
1126                            .visibility
1127                            .visible_member_for_owner_name(ctx.file, &owner, name)
1128                        {
1129                            VisibleMemberResolution::Callable(callables) => {
1130                                if let Some(callable) = callables.iter().find(|callable| {
1131                                    cpp_callable_arity(&ctx.analyzer, callable).accepts(call_arity)
1132                                }) {
1133                                    ctx.record(callable.fq_name(), field);
1134                                }
1135                            }
1136                            VisibleMemberResolution::AmbiguousKind => {
1137                                ctx.record_unproven(name, field);
1138                            }
1139                            VisibleMemberResolution::NonCallable
1140                            | VisibleMemberResolution::Missing => {}
1141                        }
1142                    }
1143                    EnclosingMemberOwnerResolution::Ambiguous => {
1144                        ctx.record_unproven(name, field);
1145                    }
1146                    EnclosingMemberOwnerResolution::Missing => {}
1147                }
1148            } else {
1149                ctx.record_unproven(name, field);
1150            }
1151        }
1152        // A bare `m(..)` is either a free function or an unqualified member call on
1153        // the enclosing class (`this`). `qualified_identifier` (`X::m`) is handled
1154        // by the type-reference case above.
1155        "identifier" | "template_function" => {
1156            let terminal = super::resolver::function_terminal_node(function);
1157            let name = node_text(terminal, ctx.source);
1158            if name.is_empty() {
1159                return;
1160            }
1161            if bindings.is_shadowed(name) {
1162                return;
1163            }
1164            if let Some(enclosing_owner) = enclosing_callable_owner(function, ctx) {
1165                let ordinary = resolve_declaring_member_owner_cached(ctx, &enclosing_owner, name);
1166                match resolve_declaring_callable_owner(
1167                    &ctx.analyzer,
1168                    ctx.visibility,
1169                    ctx.file,
1170                    ordinary,
1171                    name,
1172                    call_arity,
1173                ) {
1174                    EnclosingMemberOwnerResolution::Owner(owner)
1175                        if !same_visible_symbol(&owner, &enclosing_owner) =>
1176                    {
1177                        match ctx
1178                            .visibility
1179                            .visible_member_for_owner_name(ctx.file, &owner, name)
1180                        {
1181                            VisibleMemberResolution::Callable(callables) => {
1182                                if let Some(callable) = callables.iter().find(|callable| {
1183                                    cpp_callable_arity(&ctx.analyzer, callable).accepts(call_arity)
1184                                }) {
1185                                    ctx.record(callable.fq_name(), function);
1186                                }
1187                            }
1188                            VisibleMemberResolution::AmbiguousKind => {
1189                                ctx.record_unproven(name, function);
1190                            }
1191                            VisibleMemberResolution::NonCallable
1192                            | VisibleMemberResolution::Missing => {}
1193                        }
1194                        return;
1195                    }
1196                    EnclosingMemberOwnerResolution::Owner(_) => {
1197                        // Bare `m(..)` resolving to a method whose owner IS the
1198                        // enclosing class is a same-owner call (#1161, mirroring
1199                        // the `this->m()` fix at #1138): record it as unproven
1200                        // inbound rather than dropping it, so a member reachable
1201                        // only through bare implicit-this calls reads
1202                        // INCONCLUSIVE, never confidently dead — uniformly with
1203                        // the other languages and with the explicit-`this->m()`
1204                        // site above.
1205                        ctx.record_unproven(name, function);
1206                        return;
1207                    }
1208                    EnclosingMemberOwnerResolution::Ambiguous => {
1209                        ctx.record_unproven(name, function);
1210                        return;
1211                    }
1212                    EnclosingMemberOwnerResolution::Missing => {}
1213                }
1214            }
1215            let resolution = resolve_bare_call_target(
1216                node,
1217                function,
1218                &ctx.analyzer,
1219                ctx.visibility,
1220                &ctx.ordinary_type_imports,
1221                ctx.file,
1222                ctx.source,
1223            );
1224            match resolution {
1225                BareCallTargetResolution::FreeFunctions(units) => {
1226                    let mut recorded = HashSet::default();
1227                    for unit in units {
1228                        let fq_name = unit.fq_name();
1229                        if recorded.insert(fq_name.clone()) {
1230                            ctx.record(fq_name, terminal);
1231                        }
1232                    }
1233                }
1234                BareCallTargetResolution::Type(unit) => {
1235                    if let VisibleMemberResolution::Callable(constructors) = ctx
1236                        .visibility
1237                        .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
1238                        && let Some(constructor) = constructors.iter().find(|constructor| {
1239                            cpp_callable_arity(&ctx.analyzer, constructor).accepts(call_arity)
1240                        })
1241                    {
1242                        ctx.record(constructor.fq_name(), terminal);
1243                    } else {
1244                        ctx.record(unit.fq_name(), function);
1245                    }
1246                }
1247                BareCallTargetResolution::UnprovenFreeFunctions(_)
1248                | BareCallTargetResolution::CallableShadow
1249                | BareCallTargetResolution::Ambiguous => {}
1250                BareCallTargetResolution::Missing => {}
1251            }
1252            // Direct/self member calls are intentionally omitted above; unique inherited
1253            // callable owners are recorded, while an unresolved bare name adds no edge.
1254        }
1255        _ => {}
1256    }
1257}
1258
1259fn resolve_declaring_member_owner_cached(
1260    ctx: &mut CppScan<'_>,
1261    receiver_owner: &CodeUnit,
1262    name: &str,
1263) -> EnclosingMemberOwnerResolution {
1264    if let Some(cached) = ctx
1265        .declaring_member_cache
1266        .get(receiver_owner)
1267        .and_then(|by_name| by_name.get(name))
1268        .cloned()
1269    {
1270        return cached;
1271    }
1272    let resolution = resolve_declaring_member_owner(
1273        &ctx.analyzer,
1274        ctx.visibility,
1275        ctx.file,
1276        receiver_owner,
1277        name,
1278    );
1279    ctx.declaring_member_cache
1280        .entry(receiver_owner.clone())
1281        .or_default()
1282        .insert(name.to_string(), resolution.clone());
1283    resolution
1284}
1285
1286fn enclosing_callable_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1287    let mut current = node.parent();
1288    while let Some(parent) = current {
1289        if parent.kind() == "function_definition" {
1290            let declarator = parent.child_by_field_name("declarator")?;
1291            let function = declarator_name_node(declarator)?;
1292            if let Some(owners) = out_of_line_member_definition_owner(
1293                &ctx.analyzer,
1294                ctx.visibility,
1295                ctx.file,
1296                ctx.source,
1297                function,
1298            ) && let Some((_, owner)) = owners.innermost()
1299            {
1300                return Some(owner.clone());
1301            }
1302            break;
1303        }
1304        current = parent.parent();
1305    }
1306    ctx.enclosing_class(node.start_byte()).and_then(|fqn| {
1307        ctx.analyzer
1308            .definitions(fqn)
1309            .find(|candidate| candidate.is_class())
1310    })
1311}
1312
1313/// Whether `receiver` is the implicit-object receiver of a member call.
1314///
1315/// `reference_is_c` is the compilation language the reference is read in
1316/// (#1970): `this` is the C++ implicit object only where C++ compiles the
1317/// source, and is an ordinary identifier in a `.c` file or a header every
1318/// reaching translation unit compiles as C.
1319fn receiver_is_self_like(receiver: Node<'_>, reference_is_c: bool) -> bool {
1320    match receiver.kind() {
1321        "this" => !reference_is_c,
1322        "parenthesized_expression" | "pointer_expression" => receiver
1323            .child_by_field_name("argument")
1324            .or_else(|| receiver.named_child(0))
1325            .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
1326        _ => false,
1327    }
1328}
1329
1330/// If `node` is the `function` of a namespace-qualified free-function call, its target.
1331fn scoped_free_function(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1332    if node.kind() != "qualified_identifier" {
1333        return None;
1334    }
1335    let parent = node.parent()?;
1336    if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1337        return None;
1338    }
1339    ctx.visibility.resolve_named(
1340        ctx.file,
1341        node_text(node, ctx.source),
1342        TargetKind::FreeFunction,
1343    )
1344}
1345
1346/// If `node` is the `function` of a `X::m(..)` call, the fqn of `X`'s type.
1347fn scoped_call_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<String> {
1348    if node.kind() != "qualified_identifier" {
1349        return None;
1350    }
1351    let parent = node.parent()?;
1352    if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1353        return None;
1354    }
1355    let scope = node.child_by_field_name("scope")?;
1356    match resolve_type_node_lexically(
1357        scope,
1358        &ctx.analyzer,
1359        ctx.visibility,
1360        &ctx.ordinary_type_imports,
1361        ctx.file,
1362        ctx.source,
1363    ) {
1364        LexicalTypeResolution::Resolved { unit, .. } => Some(unit.fq_name()),
1365        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1366    }
1367}
1368
1369/// The trailing member name of a `X::m` qualified identifier.
1370fn scoped_call_member(node: Node<'_>, source: &str) -> String {
1371    node.child_by_field_name("name")
1372        .map(|name| node_text(name, source).to_string())
1373        .unwrap_or_default()
1374}
1375
1376fn receiver_type_unit(
1377    receiver: Node<'_>,
1378    ctx: &CppScan<'_>,
1379    bindings: &LocalInferenceEngine<CodeUnit>,
1380    remaining_call_depth: usize,
1381) -> Option<CodeUnit> {
1382    match receiver.kind() {
1383        "identifier" => {
1384            let name = node_text(receiver, ctx.source);
1385            // A function-like macro's replacement local shadows bindings from
1386            // the caller at the source-backed replacement range. Resolve that
1387            // structured declaration before ordinary local inference, and
1388            // keep an unresolved macro type shadowed rather than treating the
1389            // spelling as a static type.
1390            if let Some(binding) = ctx.visibility.macro_local_binding_at(
1391                ctx.file,
1392                ctx.input.root(),
1393                ctx.source,
1394                receiver.start_byte(),
1395                receiver.end_byte(),
1396            ) && binding.name == name
1397            {
1398                return binding
1399                    .proven_unit
1400                    .or_else(|| {
1401                        binding.type_node.and_then(|type_node| {
1402                            resolve_type_node_with_recovered_scope(type_node, ctx)
1403                        })
1404                    })
1405                    .or_else(|| {
1406                        binding.type_node.and_then(|type_node| {
1407                            ctx.resolve_type_node_result(type_node).ok().flatten()
1408                        })
1409                    })
1410                    .or_else(|| ctx.resolve_type(&normalize_type_text(&binding.type_name)));
1411            }
1412            // A typed local resolves to its type; otherwise the name may itself be a
1413            // type, unless it is a known (shadowed) untyped local — never reinterpret
1414            // a value as a static type.
1415            first_precise(bindings, name).or_else(|| {
1416                if !bindings.is_shadowed(name)
1417                    && let Some(unit) = global_receiver_type_unit(ctx, name)
1418                {
1419                    return Some(unit);
1420                }
1421                (!bindings.is_shadowed(name))
1422                    .then(|| resolve_type_node_with_recovered_scope(receiver, ctx))
1423                    .flatten()
1424                    .or_else(|| {
1425                        (!bindings.is_shadowed(name))
1426                            .then(|| ctx.resolve_type(name))
1427                            .flatten()
1428                    })
1429            })
1430        }
1431        "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
1432            first_precise(bindings, node_text(receiver, ctx.source))
1433        }
1434        "this" => ctx.enclosing_class(receiver.start_byte()).and_then(|fqn| {
1435            ctx.analyzer
1436                .definitions(fqn)
1437                .find(|candidate| candidate.is_class())
1438        }),
1439        // `(*p).m()` / `(p).m()` unwrap to the inner receiver.
1440        "parenthesized_expression" | "pointer_expression" => receiver
1441            .child_by_field_name("argument")
1442            .or_else(|| receiver.named_child(0))
1443            .and_then(|inner| receiver_type_unit(inner, ctx, bindings, remaining_call_depth)),
1444        "call_expression" if remaining_call_depth > 0 => infer_cpp_initializer_binding(
1445            &ctx.analyzer,
1446            ctx.visibility,
1447            ctx.file,
1448            ctx.source,
1449            receiver,
1450            Some(&|inner, _source| {
1451                receiver_type_unit(inner, ctx, bindings, remaining_call_depth - 1)
1452                    .into_iter()
1453                    .collect()
1454            }),
1455        )
1456        .and_then(|binding| binding.unit),
1457        // A nested C selector carries the declared type of its selected
1458        // field. Resolve that field through the same owner/hierarchy lookup
1459        // used by ordinary field references; this preserves synthetic owners
1460        // for named anonymous aggregates without parsing type text.
1461        "field_expression" => {
1462            let field = receiver.child_by_field_name("field")?;
1463            let name = node_text(field, ctx.source);
1464            let inner = cpp_field_expression_receiver(receiver)?;
1465            let receiver_owner = receiver_type_unit(inner, ctx, bindings, remaining_call_depth)?;
1466            let owner = match resolve_declaring_member_owner(
1467                &ctx.analyzer,
1468                ctx.visibility,
1469                ctx.file,
1470                &receiver_owner,
1471                name,
1472            ) {
1473                EnclosingMemberOwnerResolution::Owner(owner) => owner,
1474                EnclosingMemberOwnerResolution::Ambiguous
1475                | EnclosingMemberOwnerResolution::Missing => return None,
1476            };
1477            let fields = ctx
1478                .visibility
1479                .visible_members_for_owner_name(ctx.file, &owner, name)
1480                .into_iter()
1481                .filter(|candidate| candidate.is_field())
1482                .collect::<Vec<_>>();
1483            let [field_unit] = fields.as_slice() else {
1484                return None;
1485            };
1486            field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field_unit)
1487                .and_then(|binding| binding.unit)
1488        }
1489        "cast_expression" => cast_expression_type_node(receiver).and_then(|type_node| {
1490            resolve_type_node_with_recovered_scope(type_node, ctx).or_else(|| {
1491                match ctx.resolve_type_node_result(type_node) {
1492                    Ok(Some(unit)) => Some(unit),
1493                    Ok(None) => ctx.resolve_type(node_text(type_node, ctx.source)),
1494                    Err(_) => None,
1495                }
1496            })
1497        }),
1498        _ => None,
1499    }
1500}
1501
1502fn global_receiver_type_unit(ctx: &CppScan<'_>, name: &str) -> Option<CodeUnit> {
1503    let fields = ctx
1504        .visibility
1505        .named_candidates(ctx.file, name, TargetKind::GlobalField)
1506        .into_iter()
1507        .filter(has_persisted_global_field_identity)
1508        .collect::<Vec<_>>();
1509    let first = fields.first()?;
1510    let mut linkage_cache = HashMap::default();
1511    if fields.iter().skip(1).any(|field| {
1512        !same_visible_global_field_symbol(&ctx.analyzer, &mut linkage_cache, first, field)
1513    }) {
1514        return None;
1515    }
1516    let mut units = fields
1517        .into_iter()
1518        .filter_map(|field| {
1519            field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, &field)
1520                .and_then(|binding| binding.unit)
1521        })
1522        .collect::<Vec<_>>();
1523    units.dedup_by(|left, right| same_visible_symbol(left, right));
1524    match units.as_slice() {
1525        [unit] => Some(unit.clone()),
1526        _ => None,
1527    }
1528}
1529
1530fn seed_declaration(
1531    node: Node<'_>,
1532    ctx: &mut CppScan<'_>,
1533    bindings: &mut LocalInferenceEngine<CodeUnit>,
1534) {
1535    if recovered_macro_return_type_node(node, ctx.source).is_some()
1536        || crate::declarations::is_direct_recovered_exported_class_field_declaration(
1537            node, ctx.source,
1538        )
1539    {
1540        return;
1541    }
1542    match node.kind() {
1543        "parameter_declaration" | "optional_parameter_declaration" => {
1544            seed_typed_binding(node, ctx, bindings)
1545        }
1546        "declaration" | "field_declaration" => seed_variable_declaration(node, ctx, bindings),
1547        "for_range_loop" => seed_range_binding(node, ctx, bindings),
1548        "expression_statement" => seed_function_macro_local_binding(node, ctx, bindings),
1549        "assignment_expression" => seed_function_macro_container_binding(node, ctx, bindings),
1550        _ => {}
1551    }
1552}
1553
1554fn seed_function_macro_local_binding(
1555    node: Node<'_>,
1556    ctx: &CppScan<'_>,
1557    bindings: &mut LocalInferenceEngine<CodeUnit>,
1558) {
1559    let Some(binding) = ctx
1560        .visibility
1561        .function_macro_local_binding(ctx.file, node, ctx.source)
1562    else {
1563        return;
1564    };
1565    let unit = binding
1566        .type_node
1567        .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1568        .or_else(|| {
1569            binding
1570                .type_node
1571                .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1572        })
1573        .or_else(|| ctx.resolve_type(&binding.type_name));
1574    match unit {
1575        Some(unit) => bindings.seed_symbol(binding.name, unit),
1576        None => bindings.declare_shadow(binding.name),
1577    }
1578}
1579
1580fn seed_function_macro_container_binding(
1581    node: Node<'_>,
1582    ctx: &CppScan<'_>,
1583    bindings: &mut LocalInferenceEngine<CodeUnit>,
1584) {
1585    let Some(binding) =
1586        ctx.visibility
1587            .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
1588    else {
1589        return;
1590    };
1591    let unit = binding.proven_unit.or_else(|| {
1592        binding
1593            .type_node
1594            .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1595            .or_else(|| {
1596                binding
1597                    .type_node
1598                    .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1599            })
1600            .or_else(|| ctx.resolve_type(&binding.type_name))
1601    });
1602    match unit {
1603        Some(unit) => bindings.seed_symbol(binding.name, unit),
1604        None => bindings.declare_shadow(binding.name),
1605    }
1606}
1607
1608fn seed_typed_binding(
1609    node: Node<'_>,
1610    ctx: &CppScan<'_>,
1611    bindings: &mut LocalInferenceEngine<CodeUnit>,
1612) {
1613    if !parameter_belongs_to_callable_scope(node) {
1614        return;
1615    }
1616    let Some(declarator) = node.child_by_field_name("declarator") else {
1617        return;
1618    };
1619    let Some(name) = extract_variable_name(declarator, ctx.source) else {
1620        return;
1621    };
1622    let type_node = node
1623        .child_by_field_name("type")
1624        .or_else(|| first_type_child(node));
1625    seed_binding(&name, type_node, None, ctx, bindings);
1626}
1627
1628fn seed_range_binding(
1629    node: Node<'_>,
1630    ctx: &CppScan<'_>,
1631    bindings: &mut LocalInferenceEngine<CodeUnit>,
1632) {
1633    let Some(declarator) = node.child_by_field_name("declarator") else {
1634        return;
1635    };
1636    let Some(name) = extract_variable_name(declarator, ctx.source) else {
1637        return;
1638    };
1639    let type_node = node
1640        .child_by_field_name("type")
1641        .or_else(|| first_type_child(node));
1642    seed_binding(&name, type_node, None, ctx, bindings);
1643}
1644
1645fn seed_variable_declaration(
1646    node: Node<'_>,
1647    ctx: &CppScan<'_>,
1648    bindings: &mut LocalInferenceEngine<CodeUnit>,
1649) {
1650    let type_node = node
1651        .child_by_field_name("type")
1652        .or_else(|| first_type_child(node));
1653    let type_text =
1654        type_node.map(|type_node| normalize_type_text(node_text(type_node, ctx.source)));
1655    let mut cursor = node.walk();
1656    for child in node.named_children(&mut cursor) {
1657        let declarator = if child.kind() == "init_declarator" {
1658            child.child_by_field_name("declarator")
1659        } else if is_declarator_node(child) {
1660            Some(child)
1661        } else {
1662            None
1663        };
1664        let Some(declarator) = declarator else {
1665            continue;
1666        };
1667        if declarator.kind() == "function_declarator"
1668            && !constructor_style_local_declaration(
1669                ctx.visibility,
1670                ctx.file,
1671                ctx.source,
1672                declarator,
1673                type_text.as_deref(),
1674                bindings,
1675            )
1676        {
1677            if node.kind() == "declaration"
1678                && has_function_scope_ancestor(node)
1679                && let Some(name) = extract_variable_name(declarator, ctx.source)
1680            {
1681                bindings.declare_shadow(name);
1682            }
1683            continue;
1684        }
1685        let Some(name) = extract_variable_name(declarator, ctx.source) else {
1686            continue;
1687        };
1688        let value = child.child_by_field_name("value");
1689        seed_binding(&name, type_node, value, ctx, bindings);
1690    }
1691}
1692
1693fn has_function_scope_ancestor(mut node: Node<'_>) -> bool {
1694    while let Some(parent) = node.parent() {
1695        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1696            return true;
1697        }
1698        node = parent;
1699    }
1700    false
1701}
1702
1703fn seed_binding(
1704    name: &str,
1705    type_node: Option<Node<'_>>,
1706    value: Option<Node<'_>>,
1707    ctx: &CppScan<'_>,
1708    bindings: &mut LocalInferenceEngine<CodeUnit>,
1709) {
1710    if name.is_empty() {
1711        return;
1712    }
1713    // A declared type resolves directly; `auto x = new Foo()` infers from the
1714    // initializer. A declared-but-unresolved local is shadowed so a later
1715    // member access never falls back to static type resolution on its name.
1716    let declared_type =
1717        type_node.filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto");
1718    let resolved = match declared_type {
1719        Some(node) => resolve_type_node_with_recovered_scope(node, ctx).or_else(|| {
1720            match ctx.resolve_type_node_result(node) {
1721                Ok(Some(unit)) => Some(unit),
1722                Ok(None) => ctx.resolve_type(node_text(node, ctx.source)),
1723                Err(_) => None,
1724            }
1725        }),
1726        None => value.and_then(|value| infer_type_from_value(value, ctx)),
1727    };
1728    match resolved {
1729        Some(unit) => bindings.seed_symbol(name.to_string(), unit),
1730        None => bindings.declare_shadow(name.to_string()),
1731    }
1732}
1733
1734fn resolve_type_node_with_recovered_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1735    if let Some(owner) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
1736        return Some(owner);
1737    }
1738    let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
1739    let components = cpp_type_name_components(node, ctx.source)?;
1740    let global = is_globally_qualified_cpp_name(node);
1741    match ctx.visibility.resolve_type_components_lexically(
1742        &ctx.analyzer,
1743        ctx.file,
1744        &components,
1745        global,
1746        &scope,
1747    ) {
1748        LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
1749        LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1750    }
1751}
1752
1753/// Infer a class type from an initializer expression for `auto`/untyped locals.
1754fn infer_type_from_value(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1755    infer_cpp_initializer_type(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
1756}