Skip to main content

brokk_bifrost_cpp/
identity.rs

1//! Which C++ callable is which: declaration/definition roles, the linkage
2//! evidence that unifies a header declaration with its `.cpp` definition, and
3//! the #1134 resolution-time identity reconciliation built on top of both.
4//!
5//! Two things stay in `analyzer/cpp/identity.rs` on purpose:
6//!
7//! * [`cpp_header_body_files_are_related`] here reads the include closure off a
8//!   [`CppSource`]. The analysis wrapper of the same name owns the
9//!   `resolve_analyzer::<CppAnalyzer>` downcast that produces one, because the
10//!   searchtools identity block reaches this predicate through `&dyn IAnalyzer`
11//!   and no capability carries the include graph.
12//! * The moka cells that memoize [`cpp_reconcile_candidates`] per member
13//!   identifier and [`cpp_reconcile_group`] per [`CppReconcileGroupKey`] stay
14//!   on the analyzer, as does every other cache, so `IAnalyzer::update` keeps
15//!   rebuilding them wholesale.
16
17use crate::declarations::{
18    CppRecoveredExportClassIndex, cpp_member_fq, extract_function_declarator, node_text,
19    recovered_callable_body_at, recovered_class_body_at,
20};
21use crate::graph::CppGraphSource;
22use crate::graph::resolver::{VisibilityIndex, cpp_include_closure_reaches};
23use crate::graph_support::CppSource;
24use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
25use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
26use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range, SignatureMetadata};
27use brokk_bifrost_core::analyzer::query_token::QueryToken;
28use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
29use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
30use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
31use brokk_bifrost_core::hash::HashMap;
32use brokk_bifrost_core::path_utils::rel_path_string;
33use brokk_bifrost_core::profiling;
34use std::sync::Arc;
35use tree_sitter::Node;
36use tree_sitter::{Parser, Tree};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum CppCallableUnitRole {
40    DeclarationOnly,
41    Definition,
42    Both,
43    Unknown,
44}
45
46pub use brokk_bifrost_core::analyzer::cpp_facts::CppOccurrenceRole;
47pub struct CppOccurrenceClassifier {
48    tree: Tree,
49    /// The parsed text. A class role reads the recovered export-macro shapes,
50    /// and those are named from source text the tree alone does not carry.
51    source: String,
52    /// Resolved once for the whole tree rather than per classified occurrence
53    /// (#1496).
54    recovered_export_classes: CppRecoveredExportClassIndex,
55}
56
57impl CppOccurrenceClassifier {
58    pub fn new(source: &str) -> Option<Self> {
59        let mut parser = Parser::new();
60        parser
61            .set_language(&tree_sitter_cpp::LANGUAGE.into())
62            .ok()?;
63        parser.parse(source, None).map(|tree| {
64            let recovered_export_classes =
65                CppRecoveredExportClassIndex::build(tree.root_node(), source);
66            Self {
67                tree,
68                source: source.to_owned(),
69                recovered_export_classes,
70            }
71        })
72    }
73
74    pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
75        cpp_occurrence_role_for_range(
76            &self.recovered_export_classes,
77            self.tree.root_node(),
78            &self.source,
79            candidate,
80            range,
81        )
82    }
83}
84
85pub fn cpp_callable_unit_role(
86    index: &dyn CodeUnitIndex,
87    callable: &CodeUnit,
88) -> CppCallableUnitRole {
89    cpp_callable_unit_role_from_metadata(callable, index.signature_metadata(callable))
90}
91
92fn cpp_callable_unit_role_from_metadata(
93    callable: &CodeUnit,
94    metadata: impl IntoIterator<Item = SignatureMetadata>,
95) -> CppCallableUnitRole {
96    if !callable.is_callable() {
97        return CppCallableUnitRole::Unknown;
98    }
99    let mut declaration = false;
100    let mut definition = false;
101    for metadata in metadata {
102        if metadata.is_declaration_only() {
103            declaration = true;
104        } else {
105            definition = true;
106        }
107    }
108    match (declaration, definition) {
109        (true, false) => CppCallableUnitRole::DeclarationOnly,
110        (false, true) => CppCallableUnitRole::Definition,
111        (true, true) => CppCallableUnitRole::Both,
112        (false, false) => CppCallableUnitRole::Unknown,
113    }
114}
115
116pub fn cpp_indexed_callable_linkage(
117    index: &dyn CodeUnitIndex,
118    callable: &CodeUnit,
119) -> Option<CallableLinkage> {
120    let mut external = false;
121    for metadata in index.signature_metadata(callable) {
122        match metadata.callable_linkage() {
123            Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
124            Some(CallableLinkage::External) => external = true,
125            None => {}
126        }
127    }
128    external.then_some(CallableLinkage::External)
129}
130
131/// Whether `left` and `right` are the same callable seen twice.
132///
133/// `header_body_related` is the include-evidence predicate; the analysis wrapper
134/// supplies it because reaching the include graph needs the analyzer downcast
135/// this crate cannot perform.
136pub fn cpp_callable_definitions_share_identity_evidence(
137    index: &dyn CodeUnitIndex,
138    left: &CodeUnit,
139    right: &CodeUnit,
140    header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
141) -> bool {
142    left.fq_name() == right.fq_name()
143        && left.signature() == right.signature()
144        && (left.source() == right.source()
145            || (matches!(
146                cpp_indexed_callable_linkage(index, left),
147                Some(CallableLinkage::External)
148            ) && matches!(
149                cpp_indexed_callable_linkage(index, right),
150                Some(CallableLinkage::External)
151            ) && header_body_related(left.source(), right.source())))
152}
153
154/// The same evidence, with the signature-string conjunct answered by the
155/// resolved parameter comparison instead.
156///
157/// The persisted signature embeds each parameter type exactly as it was
158/// spelled, and a header declaration and its out-of-line body routinely spell
159/// one type two ways - `msg_t *` inside `namespace zmq` against `zmq::msg_t *`
160/// at file scope - so string equality alone refuses to unify the very pair this
161/// predicate exists to unify (#2010). [`VisibilityIndex::same_logical_callable`]
162/// compares the strings first and resolves the written parameter names only
163/// when they differ, so this is strictly the wider relation; every other
164/// conjunct - equal `fq_name`, external linkage on both sides, and the include
165/// evidence relating the two files - is unchanged.
166///
167/// Definition lookup uses this variant. The workspace-scale scans keep the
168/// string form above, where the candidate set is the whole index.
169pub fn cpp_callable_definitions_share_identity_evidence_with_visibility(
170    analyzer: &CppGraphSource<'_>,
171    visibility: &VisibilityIndex<'_>,
172    left: &CodeUnit,
173    right: &CodeUnit,
174    header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
175) -> bool {
176    left.fq_name() == right.fq_name()
177        && visibility.same_logical_callable(analyzer, left, right)
178        && (left.source() == right.source()
179            || (matches!(
180                cpp_indexed_callable_linkage(analyzer.index, left),
181                Some(CallableLinkage::External)
182            ) && matches!(
183                cpp_indexed_callable_linkage(analyzer.index, right),
184                Some(CallableLinkage::External)
185            ) && header_body_related(left.source(), right.source())))
186}
187
188/// Return whether `node` is one of the names declared by a range-for
189/// declarator. Follow only declarator fields. This keeps identifiers in array
190/// bounds and attributes in the range-for header as references.
191pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
192    let mut current = Some(node);
193    while let Some(candidate) = current {
194        let Some(parent) = candidate.parent() else {
195            return false;
196        };
197        if parent.kind() == "for_range_loop" {
198            return parent
199                .child_by_field_name("declarator")
200                .is_some_and(|declarator| {
201                    cpp_range_for_declarator_contains_name(declarator, node)
202                });
203        }
204        current = Some(parent);
205    }
206    false
207}
208
209/// Return whether `node` is part of a conversion operator's target type.
210///
211/// A C++ conversion operator has no return type. Its target is part of the
212/// operator declaration identity, so inverse usage analysis deliberately does
213/// not report it as a type reference (#1489). Keep census probes on the same
214/// semantic surface by recognizing only components structurally contained by
215/// tree-sitter's `operator_cast` node. Types used in the function body and
216/// ordinary or overloaded-operator return types never cross that node.
217pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
218    while let Some(parent) = node.parent() {
219        if parent.kind() == "operator_cast" {
220            return true;
221        }
222        if matches!(
223            parent.kind(),
224            "function_declarator" | "declaration" | "function_definition" | "translation_unit"
225        ) {
226            return false;
227        }
228        node = parent;
229    }
230    false
231}
232
233/// Return whether `node` is a character token that C++ recovery parsed as an
234/// unnamed parameter type in a class-scope macro invocation.
235///
236/// Tree-sitter does not expand macros. A call such as `STRING_(Name, 'x')`
237/// where declarations are expected can therefore become a malformed function
238/// declaration. Its character argument has an exact CST shape: the opening and
239/// closing quote are separate direct `ERROR` siblings around an otherwise
240/// clean `parameter_declaration(type_identifier)`. That middle identifier is
241/// neither a declaration nor a reference.
242pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
243    if node.kind() != "type_identifier" {
244        return false;
245    }
246    let Some(parameter) = node.parent() else {
247        return false;
248    };
249    if parameter.kind() != "parameter_declaration"
250        || parameter.child_by_field_name("type") != Some(node)
251        || parameter.child_by_field_name("declarator").is_some()
252        || parameter
253            .parent()
254            .is_none_or(|parent| parent.kind() != "parameter_list")
255    {
256        return false;
257    }
258
259    parameter
260        .prev_named_sibling()
261        .is_some_and(cpp_is_recovered_character_quote)
262        && parameter
263            .next_named_sibling()
264            .is_some_and(cpp_is_recovered_character_quote)
265}
266
267fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
268    node.is_error()
269        && node.child_count() == 1
270        && node
271            .child(0)
272            .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
273}
274
275/// Return whether `node` is the declarator name of a constructor or destructor
276/// -- the declaration occurrence itself, never a reference to one.
277///
278/// A declaration site is not a usage probe, so the reference differential must
279/// not seed one (#1834). The indexed-declaration-name filter the seeder already
280/// applies misses these two shapes:
281///
282/// * The identifier inside a `destructor_name`. The census proposes both
283///   `~Foo` and its inner `Foo`, while the indexed declaration name range
284///   covers only the `~Foo` span, so the inner identifier survives the filter.
285/// * A declarator the parse never recovered as a declaration. `class MACRO Foo
286///   { ... }` and a bare `MACRO_NAMESPACE_BEGIN` before `class Foo { ... }`
287///   both recover as a `function_definition` whose declarator is a lone
288///   identifier -- a shape valid C++ cannot produce -- with the class body as
289///   its `compound_statement`. Every declaration inside it that the grammar can
290///   read as an expression becomes one, so `Foo();` reads as a call of `Foo`,
291///   which the census then grades as a tier-1 forward gap.
292///
293/// Both tests are structural. Constructor calls stay references: `new Foo(...)`
294/// is a `new_expression` type, `Foo x(...)` is a declaration whose type field
295/// holds the name, and `: base_(x)` is a `field_initializer`. None of them is a
296/// `function_declarator` declarator or a callee inside a recovered class body.
297pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
298    cpp_is_declared_constructor_or_destructor_name(node)
299        || cpp_is_recovered_constructor_or_destructor_name(node, source)
300}
301
302/// The parsed-as-declared shape: the grammar's `constructor_or_destructor_
303/// declaration` and `constructor_or_destructor_definition`, both aliased to
304/// `declaration`/`function_definition` and both recognizable by the absence of
305/// a `type` field -- exactly what distinguishes a constructor or destructor
306/// from every other C++ callable, which must name a return type.
307fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
308    let mut name = node;
309    if let Some(parent) = name.parent()
310        && parent.kind() == "destructor_name"
311    {
312        name = parent;
313    }
314    // `Foo::Foo`, `A::B::Foo` and `Foo<T>::~Foo` reach the declarator through
315    // the qualified name's `name` field. The `scope` segments stay references:
316    // they name the owning type.
317    while let Some(parent) = name.parent() {
318        if parent.kind() != "qualified_identifier"
319            || parent.child_by_field_name("name") != Some(name)
320        {
321            break;
322        }
323        name = parent;
324    }
325    let Some(declarator) = name.parent() else {
326        return false;
327    };
328    if declarator.kind() != "function_declarator"
329        || declarator.child_by_field_name("declarator") != Some(name)
330    {
331        return false;
332    }
333    let Some(owner) = declarator.parent() else {
334        return false;
335    };
336    matches!(owner.kind(), "declaration" | "function_definition")
337        && owner.child_by_field_name("declarator") == Some(declarator)
338        && owner.child_by_field_name("type").is_none()
339}
340
341/// The recovered shape: a callee that names the class whose body the parse
342/// turned into a `compound_statement`.
343///
344/// Two conditions hold together, and both are needed. The nearest enclosing
345/// `function_definition` must declare a bare `identifier` -- valid C++ always
346/// declares a `function_declarator` there, so this shape only ever comes out of
347/// the class-body recovery. And the callee must name one of the identifiers in
348/// that recovery's header, which is where the class name is: `class MACRO Foo`
349/// and `class MACRO Foo : public Base` both keep `Foo` in the header even
350/// though the second leaves `Base` as the recovered declarator.
351///
352/// Together they keep genuine calls references. A recursive `f(n - 1);` sits in
353/// a real body, whose declarator is a `function_declarator`. A method body
354/// inside the recovered class body is itself a real `function_definition`, so
355/// `RAPIDJSON_ASSERT(false)` inside one keeps its own nearest owner. A macro
356/// invocation such as `DISALLOW_COPY_AND_ASSIGN(Foo);` in the recovered body
357/// does not name the class, so it stays proposed.
358fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
359    if node.kind() != "identifier" {
360        return false;
361    }
362    let Some(call) = node.parent() else {
363        return false;
364    };
365    if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
366        return false;
367    }
368    let mut current = call.parent();
369    while let Some(ancestor) = current {
370        if ancestor.kind() == "function_definition" {
371            return ancestor
372                .child_by_field_name("declarator")
373                .is_some_and(|declarator| declarator.kind() == "identifier")
374                && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
375        }
376        current = ancestor.parent();
377    }
378    false
379}
380
381/// Whether `name` is spelled by an identifier in the recovered class header --
382/// everything the recovery kept before the body it mistook for a function body.
383fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
384    let header_end = definition
385        .child_by_field_name("body")
386        .map_or_else(|| definition.end_byte(), |body| body.start_byte());
387    let mut stack = vec![definition];
388    while let Some(node) = stack.pop() {
389        if node.start_byte() >= header_end {
390            continue;
391        }
392        if matches!(
393            node.kind(),
394            "identifier" | "type_identifier" | "namespace_identifier"
395        ) && node_text(node, source) == name
396        {
397            return true;
398        }
399        let mut cursor = node.walk();
400        for child in node.named_children(&mut cursor) {
401            stack.push(child);
402        }
403    }
404    false
405}
406
407fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
408    let mut pending = vec![declarator];
409    while let Some(candidate) = pending.pop() {
410        match candidate.kind() {
411            "identifier" | "field_identifier" => {
412                if cpp_same_node(candidate, target) {
413                    return true;
414                }
415            }
416            "structured_binding_declarator" => {
417                let mut cursor = candidate.walk();
418                if candidate
419                    .named_children(&mut cursor)
420                    .any(|name| cpp_same_node(name, target))
421                {
422                    return true;
423                }
424            }
425            "pointer_declarator"
426            | "reference_declarator"
427            | "array_declarator"
428            | "attributed_declarator"
429            | "parenthesized_declarator"
430            | "function_declarator"
431            | "init_declarator" => {
432                if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
433                    pending.push(inner);
434                }
435            }
436            _ => {}
437        }
438    }
439    false
440}
441
442fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
443    node.child_by_field_name("declarator").or_else(|| {
444        let mut cursor = node.walk();
445        node.named_children(&mut cursor).find(|child| {
446            matches!(
447                child.kind(),
448                "identifier"
449                    | "field_identifier"
450                    | "structured_binding_declarator"
451                    | "pointer_declarator"
452                    | "reference_declarator"
453                    | "array_declarator"
454                    | "attributed_declarator"
455                    | "parenthesized_declarator"
456                    | "function_declarator"
457                    | "init_declarator"
458            )
459        })
460    })
461}
462
463fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
464    left.id() == right.id()
465        && left.start_byte() == right.start_byte()
466        && left.end_byte() == right.end_byte()
467}
468
469/// Include evidence relates one header declaration to one implementation file
470/// without pretending that every external name in a workspace belongs to one
471/// linker unit.
472///
473/// The question is whether the implementation file's translation unit sees the
474/// declaring header at all, and a translation unit sees every header in its
475/// `#include` closure, not only the ones it names itself. `llama-vocab.cpp`
476/// defines `llama_tokenize`, declared in `llama.h`, while including only
477/// `llama-vocab.h`, which includes `llama.h`; reading the direct include list
478/// refused that pair and left definition navigation with a bodiless
479/// declaration (#2909).
480///
481/// [`cpp_include_closure_reaches`] is that closure. It resolves each include
482/// the way visibility does, to a unique target or to nothing, so two headers
483/// sharing a basename still relate only to the translation units that
484/// unambiguously name them.
485pub fn cpp_header_body_files_are_related(
486    source: &dyn CppSource,
487    token: QueryToken<'_>,
488    left: &ProjectFile,
489    right: &ProjectFile,
490) -> bool {
491    let (header, implementation) = if cpp_source_path_is_header(left) {
492        (left, right)
493    } else if cpp_source_path_is_header(right) {
494        (right, left)
495    } else {
496        return false;
497    };
498    if cpp_source_path_is_header(implementation) {
499        return false;
500    }
501    cpp_include_closure_reaches(source, token, implementation, header)
502}
503
504pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
505    let path = rel_path_string(source).to_ascii_lowercase();
506    matches!(
507        path.rsplit('.').next(),
508        Some("h" | "hin" | "hh" | "hpp" | "hxx")
509    )
510}
511
512pub(crate) fn cpp_occurrence_role_for_node(
513    node: Node<'_>,
514    candidate: &CodeUnit,
515) -> CppOccurrenceRole {
516    if !candidate.is_callable() && !candidate.is_class() {
517        return CppOccurrenceRole::Both;
518    }
519    if candidate.is_callable() {
520        return if subtree_contains(node, |descendant| {
521            descendant.kind() == "function_definition"
522                && descendant.child_by_field_name("body").is_some()
523        }) {
524            CppOccurrenceRole::Definition
525        } else {
526            CppOccurrenceRole::DeclarationOnly
527        };
528    }
529    if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
530        return CppOccurrenceRole::Definition;
531    }
532    if !subtree_contains(node, |descendant| {
533        matches!(
534            descendant.kind(),
535            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
536        )
537    }) {
538        return CppOccurrenceRole::Both;
539    }
540    if subtree_contains(node, |descendant| {
541        matches!(
542            descendant.kind(),
543            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
544        ) && descendant.child_by_field_name("body").is_some()
545    }) {
546        CppOccurrenceRole::Definition
547    } else {
548        CppOccurrenceRole::DeclarationOnly
549    }
550}
551
552pub fn cpp_occurrence_role_for_range(
553    recovered_export_classes: &CppRecoveredExportClassIndex,
554    root: Node<'_>,
555    source: &str,
556    candidate: &CodeUnit,
557    range: &Range,
558) -> CppOccurrenceRole {
559    if !candidate.is_callable() && !candidate.is_class() {
560        return CppOccurrenceRole::Both;
561    }
562    let Some(node) = cpp_declaration_node_for_range(root, range) else {
563        return CppOccurrenceRole::Unknown;
564    };
565    if candidate.is_callable() {
566        if subtree_contains(node, |descendant| {
567            descendant.kind() == "function_definition"
568                && descendant.child_by_field_name("body").is_some()
569        }) {
570            return CppOccurrenceRole::Definition;
571        }
572        // A callable recovered from a mangled region owns no node of its own,
573        // so the climb above landed on the container the parser left -- an
574        // access label, an `ERROR`, a statement -- and the scan answered for
575        // whatever else that container holds. Ask the recovery about this exact
576        // range instead, the same way a recovered class is asked below. An
577        // ordinary declaration lands on a declaration node and never gets here.
578        if !matches!(
579            node.kind(),
580            "declaration" | "field_declaration" | "function_definition"
581        ) && let Some(has_body) = recovered_callable_body_at(source, range)
582        {
583            return if has_body {
584                CppOccurrenceRole::Definition
585            } else {
586                CppOccurrenceRole::DeclarationOnly
587            };
588        }
589        return CppOccurrenceRole::DeclarationOnly;
590    }
591    if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
592        return CppOccurrenceRole::Definition;
593    }
594    // A recovered export-macro class owns no node of its own, so the fallback
595    // climb lands on the enclosing container and the specifier scan below would
596    // answer for whatever else that container holds. In a header where every
597    // class is macro decorated, no plain specifier has a body and the scan
598    // calls every class a forward declaration. Ask the recovery shapes about
599    // this exact range first; they are the same answer the resolver's
600    // declaration strength uses.
601    if let Some(has_body) = recovered_class_body_at(
602        recovered_export_classes,
603        root,
604        source,
605        candidate.identifier(),
606        range,
607    ) {
608        return if has_body {
609            CppOccurrenceRole::Definition
610        } else {
611            CppOccurrenceRole::DeclarationOnly
612        };
613    }
614    if !subtree_contains(node, |descendant| {
615        matches!(
616            descendant.kind(),
617            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
618        )
619    }) {
620        return CppOccurrenceRole::Both;
621    }
622    if subtree_contains(node, |descendant| {
623        matches!(
624            descendant.kind(),
625            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
626        ) && descendant.child_by_field_name("body").is_some()
627    }) {
628        CppOccurrenceRole::Definition
629    } else {
630        CppOccurrenceRole::DeclarationOnly
631    }
632}
633
634/// Whether the occurrence at `range` is a pure-virtual member declaration
635/// (`virtual T f() = 0;`).
636///
637/// A pure virtual is the C++ spelling of an abstract method: `= 0` says the
638/// declaration has no body, so the declaration is itself the definition site.
639/// Definition navigation answers with it the way the JVM adapters answer an
640/// interface method with its declaration, instead of reporting that the
641/// candidates hold no implementation body (#2178). A pure virtual may still
642/// carry an out-of-line body; that occurrence is a real definition and is
643/// selected on its own role.
644pub fn cpp_range_is_pure_virtual_declaration(root: Node<'_>, source: &str, range: &Range) -> bool {
645    let Some(node) = cpp_declaration_node_for_range(root, range) else {
646        return false;
647    };
648    let mut current = Some(node);
649    while let Some(node) = current {
650        match node.kind() {
651            "field_declaration" => {
652                // `int data = 0;` and `int (*hook)() = 0;` share this
653                // `default_value` shape, so the declarator has to be a real
654                // function declarator rather than a function-pointer member.
655                return node
656                    .child_by_field_name("default_value")
657                    .is_some_and(|value| {
658                        value.kind() == "number_literal" && node_text(value, source) == "0"
659                    })
660                    && node
661                        .child_by_field_name("declarator")
662                        .and_then(extract_function_declarator)
663                        .is_some();
664            }
665            // Anything at or above the member list is a different construct:
666            // the occurrence range names the declaration or one of its parts.
667            "field_declaration_list" | "function_definition" | "translation_unit" => return false,
668            _ => current = node.parent(),
669        }
670    }
671    false
672}
673
674fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
675    node_for_exact_range(root, range).or_else(|| {
676        root.descendant_for_byte_range(range.start_byte, range.end_byte)
677            .and_then(|mut node| {
678                while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
679                    node = node.parent()?;
680                }
681                Some(node)
682            })
683    })
684}
685
686/// The #1134 resolution-time identity-reconciliation overlay for one queried
687/// canonical `fq_name`.
688///
689/// For each out-of-line member definition whose per-file provisional identity
690/// the include-visible class table re-keys to this name, it holds a *re-keyed*
691/// `CodeUnit` -- a synthetic unit carrying the canonical identity but the
692/// definition's real `.cpp` source -- so a canonical query resolves the
693/// definition alongside its header declaration across every resolution surface
694/// (`definitions`, source blocks, occurrence roles, canonical selectors). The
695/// re-keyed unit is not in the store, so `provisional_of` maps it back to the
696/// stored provisional unit for range and signature-metadata lookups.
697#[derive(Default)]
698pub struct CppReconciledDefinitionIndex {
699    /// Re-keyed definitions belonging under the queried canonical `fq_name`.
700    pub rekeyed: Vec<CodeUnit>,
701    /// Re-keyed unit -> the stored provisional unit its indexed data lives under.
702    pub provisional_of: HashMap<CodeUnit, CodeUnit>,
703}
704
705/// Which candidates one reconcile group covers: a member identifier, and the
706/// terminal owner component that #1566's pre-filter admits.
707///
708/// This -- not the queried fq name -- is the unit reconciliation is a function
709/// of, and keying the memo by it is the #1908 fix. The old key was the queried
710/// fq name, so a bare identifier that 1,277 distinct owners answer produced
711/// 1,277 distinct keys, none of which ever hit, each re-running the identical
712/// `lookup_candidates_by_identifier` store read and re-scanning the identical
713/// 2,898-candidate set: 3.70M candidate evaluations for one request.
714///
715/// The owner terminal stays in the key rather than being dropped for a plain
716/// per-identifier map. Dropping it would mean reconciling every same-named
717/// candidate in the workspace on the first query for that identifier, which is
718/// exactly the cost #1566 removed -- chromium paid ~75 s per member query that
719/// way. `reconcile_skips_same_named_members_of_unrelated_classes_1566` pins it.
720#[derive(Debug, Clone, PartialEq, Eq, Hash)]
721pub struct CppReconcileGroupKey {
722    /// The queried name's terminal segment: the member identifier the
723    /// persisted identifier index is probed with.
724    pub member_identifier: String,
725    /// The terminal component of the queried name's penultimate segment, or
726    /// `None` for a single-segment (bare) query, where #1566's pre-filter is
727    /// inert and every candidate has to be reconciled.
728    pub owner_terminal: Option<String>,
729}
730
731/// Which member identifier and owner terminal a queried canonical name asks
732/// about, or `None` when the name has no terminal segment to probe with.
733///
734/// Parsed through the sanctioned input-edge parser rather than split here, and
735/// note `$` is not a segment boundary for it -- a nested owner chain stays one
736/// segment, so the terminal really is the member.
737pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
738    let interner = segment_interner();
739    let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
740    let (member_identifier, _) = interner.resolve(query_fq.last()?);
741    if member_identifier.is_empty() {
742        return None;
743    }
744    // #1566 owner-terminal pre-filter: the reconciler only re-partitions a
745    // candidate's qualifier -- the class chain it emits is always a suffix
746    // of the candidate's owner segments (`reconcile.rs`) -- so the terminal
747    // `$` component of any identity it can produce equals the candidate's
748    // terminal owner segment. A candidate whose terminal owner differs
749    // from the queried name's penultimate segment can therefore never
750    // re-key onto it, and skipping it avoids the role check and, on
751    // whale repos, an include-closure class-table build per same-named
752    // candidate in the repo.
753    let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
754        let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
755        // fqname-M4: the input-edge parser above deliberately keeps a nested
756        // owner chain as one `$`-joined segment (no structured sub-segments
757        // exist at this surface), so the terminal component must come from
758        // the raw text.
759        text.rsplit_once('$')
760            .map_or(text, |(_, tail)| tail)
761            .to_string()
762    });
763    Some(CppReconcileGroupKey {
764        member_identifier: member_identifier.to_string(),
765        owner_terminal,
766    })
767}
768
769/// Every callable declaration in the workspace sharing one member identifier,
770/// bucketed by its terminal owner segment.
771///
772/// One store read and one pass over the candidate set per identifier, memoized
773/// on the analyzer. Before #1908 both were re-run once per queried fq name.
774pub struct CppReconcileCandidates {
775    by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
776    /// Every bucketed candidate, in the order the sorted candidate set
777    /// produced them. What a bare query has to reconcile, since #1566's
778    /// pre-filter cannot narrow it.
779    all: Vec<CodeUnit>,
780}
781
782impl CppReconcileCandidates {
783    /// The candidates a group key admits: one owner-terminal bucket, or every
784    /// candidate for a bare query.
785    fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
786        match &key.owner_terminal {
787            Some(owner_terminal) => self
788                .by_owner_terminal
789                .get(owner_terminal)
790                .map_or(&[][..], Vec::as_slice),
791            None => &self.all,
792        }
793    }
794
795    /// Every bucketed candidate, once. What a cache weigher has to charge for.
796    pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
797        self.all.iter()
798    }
799
800    /// How many bucket entries reference those candidates. A candidate with no
801    /// owner segment is in no bucket, so this is not `len`.
802    pub fn bucketed_len(&self) -> usize {
803        self.by_owner_terminal.values().map(Vec::len).sum()
804    }
805}
806
807/// Bucket an already-retrieved identifier cohort for reconciliation.
808///
809/// The analysis crate's relational executor obtains this cohort in the same
810/// batch as the caller's questions. Keeping retrieval outside this semantic
811/// function prevents the C++ crate from choosing a second, legacy data-access
812/// path while preserving one implementation of the structured owner bucketing.
813pub fn cpp_reconcile_candidates_from_units(
814    candidates: impl IntoIterator<Item = CodeUnit>,
815    keep_going: &dyn Fn() -> bool,
816) -> Option<CppReconcileCandidates> {
817    let mut candidates = candidates.into_iter().collect::<Vec<_>>();
818    candidates.sort();
819    candidates.dedup();
820    let interner = segment_interner();
821    let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
822    let mut all = Vec::new();
823    for (index, unit) in candidates.into_iter().enumerate() {
824        // Bucketing is a segment walk per candidate, cheap next to the role
825        // check and class-table build the groups pay, so the poll runs per
826        // batch rather than per candidate.
827        if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
828            return None;
829        }
830        if !unit.is_callable() {
831            continue;
832        }
833        let owner_terminal = unit
834            .fq()
835            .segments()
836            .iter()
837            .filter_map(|&segment| {
838                let (text, kind) = interner.resolve(segment);
839                // Candidate fq segments carry real boundaries (each nested
840                // class is its own `SegmentKind::Nested` segment), so the
841                // segment text is already the terminal component.
842                matches!(
843                    kind,
844                    SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
845                )
846                .then_some(text)
847            })
848            .last();
849        if let Some(owner_terminal) = owner_terminal {
850            by_owner_terminal
851                .entry(owner_terminal.to_string())
852                .or_default()
853                .push(unit.clone());
854        }
855        all.push(unit);
856    }
857    Some(CppReconcileCandidates {
858        by_owner_terminal,
859        all,
860    })
861}
862
863/// How many candidates the bucketing pass walks between deadline polls.
864const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
865
866/// Reconcile every candidate one group key admits, grouped by the canonical
867/// `fq_name` each re-keyed definition belongs under.
868///
869/// Deliberately **not** a workspace-wide index: building one would need a full
870/// declaration scan, and a warm forward lookup must not trigger one
871/// (`tests/analyzer_persistence.rs`'s candidate-bounded contract). Instead each
872/// group reconciles only the candidates the persisted terminal identifier
873/// index already offers, which is the same bounded lookup the ordinary
874/// resolution path uses.
875///
876/// A definition whose reconciled identity equals its provisional one (the
877/// overwhelming majority, including genuine `ns1::ns2::Klass::method` namespace
878/// chains) contributes nothing.
879///
880/// `None` means `keep_going` went false mid-scan. Nothing may be memoized
881/// then; see [`cpp_reconcile_candidates`].
882pub fn cpp_reconcile_group(
883    cpp: &dyn CppSource,
884    token: QueryToken<'_>,
885    key: &CppReconcileGroupKey,
886    candidates: &CppReconcileCandidates,
887    keep_going: &dyn Fn() -> bool,
888    on_candidate: &dyn Fn(),
889) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
890    let _scope = profiling::scope_with(|| {
891        format!(
892            "cpp.reconciled.build[{}#{}]",
893            key.member_identifier,
894            key.owner_terminal.as_deref().unwrap_or("*")
895        )
896    });
897    let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
898    let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
899    for unit in candidates.for_group(key) {
900        // Per candidate, not per batch: each iteration below can run a role
901        // check (0.265 ms mean in the #1908 trace) and an include-visible
902        // class-table read, so an atomic load per iteration is free by
903        // comparison.
904        if !keep_going() {
905            return None;
906        }
907        on_candidate();
908        // Lazy: `fq_name` clones a String, and this loop runs once per
909        // same-named candidate the group admits.
910        let _candidate =
911            profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
912        let role = {
913            let _role = profiling::scope("cpp.reconcile.role");
914            cpp.stored_callable_unit_role(unit)
915        };
916        if !matches!(
917            role,
918            CppCallableUnitRole::Definition | CppCallableUnitRole::Both
919        ) {
920            continue;
921        }
922        let Some(reconciled) =
923            cpp_reconcile_definition_identity(cpp, token, unit, &mut using_by_file)
924        else {
925            continue;
926        };
927        let canonical_fq = reconciled.fq_name();
928        // A candidate that already carries the canonical identity is the
929        // stored declaration, not a re-keying of it. Before #1908 this read
930        // `unit.fq_name() == fq_name` against the queried name, checked before
931        // the reconcile; against the group's canonical key it is the same
932        // predicate for the same (query, candidate) pair, because a candidate
933        // only ever lands under its own reconciled identity.
934        if unit.fq_name() == canonical_fq {
935            continue;
936        }
937        // Re-key onto the canonical identity while keeping the definition's
938        // real `.cpp` source and signature, so it resolves as a definition
939        // alongside its header declaration under the canonical `fq_name`.
940        // The structured `FqName` is rebuilt from the *canonical* package and
941        // owner chain through the same emission helper extraction uses, so
942        // the re-keyed unit carries real segment boundaries: owner lookup
943        // (`default_parent_fq_name`) is a pure segment pop, where an empty
944        // `fq` would mean "no owner" rather than "not yet migrated".
945        let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
946        let fq = cpp_member_fq(&reconciled.package, &short_name);
947        let rekeyed = CodeUnit::with_signature_and_fq(
948            unit.source().clone(),
949            unit.kind(),
950            reconciled.package,
951            short_name,
952            unit.signature().map(str::to_string),
953            unit.is_synthetic(),
954            fq,
955        );
956        let index = groups.entry(canonical_fq).or_default();
957        index.rekeyed.push(rekeyed.clone());
958        index.provisional_of.insert(rekeyed, unit.clone());
959    }
960    Some(
961        groups
962            .into_iter()
963            .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
964            .collect(),
965    )
966}
967
968/// Reconcile one out-of-line member definition's provisional identity against
969/// the class table visible to its file. Returns `None` for anything that is
970/// not a re-keyable out-of-line member or that the class table does not
971/// confirm. A one-segment class qualifier is valid when a structured using
972/// namespace and the visible class table confirm its package.
973fn cpp_reconcile_definition_identity(
974    cpp: &dyn CppSource,
975    token: QueryToken<'_>,
976    unit: &CodeUnit,
977    using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
978) -> Option<ReconciledIdentity> {
979    // Read the full source-order qualifier off the definition's *structured*
980    // `FqName` -- the namespace (`Package`) segments followed by the
981    // class-nesting (`Type`/`Nested`) ones, with the terminal `Member` as the
982    // member name. The segment boundaries were recorded at extraction, so
983    // nothing here re-infers them by splitting the rendered name on a guessed
984    // delimiter (the shape `tests/no_stringly_name_parsing.rs` guards). The
985    // reconciler then re-partitions this whole sequence against the class
986    // table, so extraction need not have decided where the namespace ends and
987    // the class chain begins.
988    let interner = segment_interner();
989    let mut provisional_owner_segments: Vec<&str> = Vec::new();
990    let mut member: Option<&str> = None;
991    for &segment in unit.fq().segments() {
992        let (text, kind) = interner.resolve(segment);
993        match kind {
994            SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
995                // A `Member` is always terminal in a cpp callable's chain; a
996                // qualifier segment after one would mean the identity is not
997                // the plain `namespace... class... member` shape this handles.
998                if member.is_some() {
999                    return None;
1000                }
1001                if !text.is_empty() {
1002                    provisional_owner_segments.push(text);
1003                }
1004            }
1005            SegmentKind::Member => member = Some(text),
1006            _ => return None,
1007        }
1008    }
1009    let member = member?;
1010    // Existing multi-segment reconciliation uses the provisional structured
1011    // FqName because it preserves template-owner normalization. Read the
1012    // declarator only for the previously unsupported one-segment shape, where
1013    // extraction can prepend a guessed using namespace.
1014    let structured_owner_segments = cpp_structured_out_of_line_owner_segments(cpp, token, unit)
1015        .filter(|segments| segments.len() == 1);
1016    let owner_segments = structured_owner_segments.as_ref().map_or_else(
1017        || provisional_owner_segments,
1018        |segments| segments.iter().map(String::as_str).collect(),
1019    );
1020    if owner_segments.is_empty() {
1021        return None;
1022    }
1023
1024    let using = using_by_file
1025        .entry(unit.source().clone())
1026        .or_insert_with(|| {
1027            Arc::new(
1028                cpp.declaration_source_facts(token, unit.source())
1029                    .map(|source| {
1030                        source
1031                            .facts
1032                            .using_namespaces
1033                            .iter()
1034                            .map(|(_, name)| name.clone())
1035                            .collect()
1036                    })
1037                    .unwrap_or_default(),
1038            )
1039        })
1040        .clone();
1041    let mut namespace_candidates: Vec<&str> = vec![""];
1042    namespace_candidates.extend(using.iter().map(String::as_str));
1043
1044    let visible = {
1045        let _visible = profiling::scope_with(|| {
1046            format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
1047        });
1048        cpp.visible_type_units(unit.source())
1049    };
1050    let class_table: Vec<VisibleClass> = visible
1051        .iter()
1052        .filter(|candidate| candidate.is_class())
1053        .map(|candidate| VisibleClass {
1054            package: candidate.package_name(),
1055            nested_short_name: candidate.short_name(),
1056        })
1057        .collect();
1058
1059    reconcile_out_of_line_member_identity(
1060        &owner_segments,
1061        member,
1062        &namespace_candidates,
1063        &class_table,
1064    )
1065}
1066
1067/// Read the owner qualifier from the definition declarator itself.
1068///
1069/// Per-file extraction can prepend one guessed using namespace to a bare
1070/// `Class::member` definition. That provisional package is not source syntax.
1071/// Reconciliation must compare the real qualifier against every structured
1072/// using target and let the visible class table select the namespace.
1073fn cpp_structured_out_of_line_owner_segments(
1074    cpp: &dyn CppSource,
1075    token: QueryToken<'_>,
1076    unit: &CodeUnit,
1077) -> Option<Vec<String>> {
1078    // Reconciliation consumes the provisional physical mount. Asking for the
1079    // published unit's merged properties here would re-enter reconciliation.
1080    let source = cpp.declaration_source_facts(token, unit.source())?;
1081    let mut owners = source
1082        .for_unit(unit)
1083        .filter(|fact| !fact.written_owner.is_empty());
1084    let first = &owners.next()?.written_owner;
1085    owners
1086        .all(|fact| &fact.written_owner == first)
1087        .then(|| first.clone())
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092    use super::*;
1093
1094    fn parse_cpp(source: &str) -> Tree {
1095        let mut parser = Parser::new();
1096        parser
1097            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1098            .expect("cpp language");
1099        parser.parse(source, None).expect("cpp tree")
1100    }
1101
1102    fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1103        let end = start + text.len();
1104        assert_eq!(&source[start..end], text, "the probe must name the token");
1105        let node = tree
1106            .root_node()
1107            .named_descendant_for_byte_range(start, end)
1108            .expect("a node spans the probed range");
1109        assert_eq!(
1110            (node.start_byte(), node.end_byte()),
1111            (start, end),
1112            "the probed range must be exactly one node: {}",
1113            node.to_sexp()
1114        );
1115        cpp_is_constructor_or_destructor_declarator_name(node, source)
1116    }
1117
1118    fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1119        let end = start + text.len();
1120        assert_eq!(&source[start..end], text, "the probe must name the token");
1121        let node = tree
1122            .root_node()
1123            .named_descendant_for_byte_range(start, end)
1124            .expect("a node spans the probed range");
1125        assert_eq!(
1126            (node.start_byte(), node.end_byte()),
1127            (start, end),
1128            "the probed range must be exactly one node: {}",
1129            node.to_sexp()
1130        );
1131        cpp_is_conversion_operator_target_type(node)
1132    }
1133
1134    fn is_recovered_macro_character_type(
1135        tree: &Tree,
1136        source: &str,
1137        start: usize,
1138        text: &str,
1139    ) -> bool {
1140        let end = start + text.len();
1141        assert_eq!(&source[start..end], text, "the probe must name the token");
1142        let node = tree
1143            .root_node()
1144            .named_descendant_for_byte_range(start, end)
1145            .expect("a node spans the probed range");
1146        cpp_is_recovered_macro_character_token_type(node)
1147    }
1148
1149    #[test]
1150    fn recovered_macro_character_tokens_are_not_type_references() {
1151        let source = concat!(
1152            "struct I {};\n",
1153            "#define STRING_TOKEN_(name, ...)\n",
1154            "struct Schema {\n",
1155            "  STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1156            "  void ordinary(I value);\n",
1157            "  void malformed(I value, @);\n",
1158            "  void use() { I value; consume('I'); }\n",
1159            "};\n",
1160        );
1161        let tree = parse_cpp(source);
1162        let recovered_m = source.find("'m'").expect("recovered m") + 1;
1163        let recovered_i = source.find("'I'").expect("recovered I") + 1;
1164        for (label, start, text) in [
1165            ("lowercase character token", recovered_m, "m"),
1166            ("uppercase character token", recovered_i, "I"),
1167        ] {
1168            assert!(
1169                is_recovered_macro_character_type(&tree, source, start, text),
1170                "{label} must match the exact recovery role"
1171            );
1172        }
1173
1174        let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1175        let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1176        let malformed =
1177            source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1178        let local = source
1179            .find("I value; consume")
1180            .expect("local type reference");
1181        let expression_character = source.rfind("'I'").expect("expression character") + 1;
1182        for (label, start, text) in [
1183            ("unquoted macro argument", macro_first_argument, "MaxItems"),
1184            ("ordinary parameter type", ordinary, "I"),
1185            ("parameter beside another error", malformed, "I"),
1186            ("local type reference", local, "I"),
1187            ("expression character literal", expression_character, "I"),
1188        ] {
1189            assert!(
1190                !is_recovered_macro_character_type(&tree, source, start, text),
1191                "{label} must remain outside the recovery role"
1192            );
1193        }
1194    }
1195
1196    #[test]
1197    fn conversion_operator_target_components_are_identity_syntax_only() {
1198        let source = concat!(
1199            "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1200            "using other::Target;\n",
1201            "struct Source {\n",
1202            "  operator Target() const;\n",
1203            "  operator other::Target() const { return other::Target{}; }\n",
1204            "  template<class T> operator other::Box<T>() const { return {}; }\n",
1205            "  operator other::Target const&() const;\n",
1206            "  operator other::Target*() const;\n",
1207            "  other::Target ordinary() const {\n",
1208            "    return reinterpret_cast<other::Target&>(*this);\n",
1209            "  }\n",
1210            "  other::Target operator+() const { return {}; }\n",
1211            "};\n",
1212        );
1213        let tree = parse_cpp(source);
1214
1215        let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1216        let qualified = source
1217            .find("operator other::Target()")
1218            .expect("qualified target")
1219            + "operator ".len();
1220        let template = source
1221            .find("operator other::Box<T>")
1222            .expect("template target")
1223            + "operator ".len();
1224        let cv_reference = source
1225            .find("operator other::Target const&")
1226            .expect("cv-reference target")
1227            + "operator other::".len();
1228        let pointer = source
1229            .find("operator other::Target*")
1230            .expect("pointer target")
1231            + "operator other::".len();
1232
1233        for (label, start, text) in [
1234            ("bare target", bare, "Target"),
1235            ("qualified target scope", qualified, "other"),
1236            (
1237                "qualified target name",
1238                qualified + "other::".len(),
1239                "Target",
1240            ),
1241            ("template target scope", template, "other"),
1242            ("template target name", template + "other::".len(), "Box"),
1243            (
1244                "template target argument",
1245                template + "other::Box<".len(),
1246                "T",
1247            ),
1248            ("cv-reference target", cv_reference, "Target"),
1249            ("pointer target", pointer, "Target"),
1250        ] {
1251            assert!(
1252                is_conversion_target(&tree, source, start, text),
1253                "the {label} at byte {start} belongs to the conversion identity"
1254            );
1255        }
1256
1257        let ordinary_return = source
1258            .find("other::Target ordinary")
1259            .expect("ordinary return");
1260        let body_cast = source
1261            .find("reinterpret_cast<other::Target")
1262            .expect("body cast")
1263            + "reinterpret_cast<".len();
1264        let overloaded_return = source
1265            .find("other::Target operator+")
1266            .expect("overloaded operator return");
1267        for (label, start) in [
1268            ("ordinary return type", ordinary_return),
1269            ("body cast target", body_cast),
1270            ("overloaded-operator return type", overloaded_return),
1271        ] {
1272            assert!(
1273                !is_conversion_target(&tree, source, start, "other"),
1274                "the {label} at byte {start} stays a reference"
1275            );
1276        }
1277    }
1278
1279    /// The parsed-as-declared shape, in class and out of line. The names that
1280    /// surround a declarator stay references: the owning scope of an out-of-line
1281    /// definition, a parameter type that happens to be the class, and the class
1282    /// name itself.
1283    #[test]
1284    fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1285        let source = concat!(
1286            "class Foo {\n",
1287            "public:\n",
1288            "  Foo();\n",
1289            "  Foo(const Foo&);\n",
1290            "  ~Foo();\n",
1291            "  void m();\n",
1292            "};\n",
1293            "Foo::Foo() {}\n",
1294            "Foo::~Foo() {}\n",
1295            "void Foo::m() {}\n",
1296        );
1297        let tree = parse_cpp(source);
1298
1299        for (label, start, text) in [
1300            (
1301                "constructor declaration",
1302                source.find("Foo();").expect("ctor"),
1303                "Foo",
1304            ),
1305            (
1306                "copy constructor declaration",
1307                source.find("Foo(const Foo&);").expect("copy ctor"),
1308                "Foo",
1309            ),
1310            (
1311                "destructor name",
1312                source.find("~Foo();").expect("dtor"),
1313                "~Foo",
1314            ),
1315            (
1316                "identifier inside the destructor name",
1317                source.find("~Foo();").expect("dtor") + "~".len(),
1318                "Foo",
1319            ),
1320            (
1321                "out-of-line constructor definition name",
1322                source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1323                "Foo",
1324            ),
1325            (
1326                "out-of-line destructor definition name",
1327                source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1328                "~Foo",
1329            ),
1330        ] {
1331            assert!(
1332                is_declarator_name(&tree, source, start, text),
1333                "the {label} at byte {start} is a declaration occurrence"
1334            );
1335        }
1336
1337        for (label, start, text) in [
1338            (
1339                "class name",
1340                source.find("class Foo {").expect("class") + "class ".len(),
1341                "Foo",
1342            ),
1343            (
1344                "parameter type",
1345                source.find("const Foo&").expect("parameter type") + "const ".len(),
1346                "Foo",
1347            ),
1348            (
1349                "owning scope of an out-of-line constructor",
1350                source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1351                "Foo",
1352            ),
1353            (
1354                "owning scope of an out-of-line destructor",
1355                source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1356                "Foo",
1357            ),
1358            (
1359                "out-of-line method name",
1360                source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1361                "m",
1362            ),
1363        ] {
1364            assert!(
1365                !is_declarator_name(&tree, source, start, text),
1366                "the {label} at byte {start} stays a reference"
1367            );
1368        }
1369    }
1370
1371    /// Constructor CALL sites are references. `new D(...)`, the direct
1372    /// initialization `D x(...)`, a member initializer and a bare temporary
1373    /// statement all name the type, and none of them is a declarator.
1374    #[test]
1375    fn constructor_call_sites_stay_references() {
1376        let source = concat!(
1377            "struct B { B(int); };\n",
1378            "struct D : B {\n",
1379            "  D(int x) : B(x), base_(x) {}\n",
1380            "  int base_;\n",
1381            "};\n",
1382            "void g() {\n",
1383            "  D* p = new D(1);\n",
1384            "  D x(2);\n",
1385            "  D(3);\n",
1386            "  g();\n",
1387            "}\n",
1388        );
1389        let tree = parse_cpp(source);
1390
1391        let inline_declarator = source.find("D(int x)").expect("inline constructor");
1392        assert!(
1393            is_declarator_name(&tree, source, inline_declarator, "D"),
1394            "an inline constructor definition name is still a declarator"
1395        );
1396
1397        for (label, start, text) in [
1398            (
1399                "base member initializer",
1400                source.find(": B(x)").expect("base initializer") + ": ".len(),
1401                "B",
1402            ),
1403            (
1404                "field member initializer",
1405                source.find("base_(x) {}").expect("field initializer"),
1406                "base_",
1407            ),
1408            (
1409                "new expression type",
1410                source.find("new D(1)").expect("new expression") + "new ".len(),
1411                "D",
1412            ),
1413            (
1414                "direct initialization type",
1415                source.find("D x(2)").expect("direct initialization"),
1416                "D",
1417            ),
1418            (
1419                "temporary construction statement",
1420                source.find("D(3)").expect("temporary"),
1421                "D",
1422            ),
1423            (
1424                "recursive call in a real body",
1425                source.find("g();").expect("recursive call"),
1426                "g",
1427            ),
1428        ] {
1429            assert!(
1430                !is_declarator_name(&tree, source, start, text),
1431                "the {label} at byte {start} is a reference"
1432            );
1433        }
1434    }
1435
1436    /// The recovered shape (#1834): an export macro between `class` and the
1437    /// class name makes the parse read the class body as a function body and
1438    /// every constructor declaration in it as a call of the class's own name.
1439    /// The bodies the recovery left intact keep their references.
1440    #[test]
1441    fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1442        let source = concat!(
1443            "class SAMPLE_EXPORT Properties {\n",
1444            "  public:\n",
1445            "    Properties();\n",
1446            "    DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1447            "    int size() const;\n",
1448            "    int total() { return size(); }\n",
1449            "};\n",
1450        );
1451        let tree = parse_cpp(source);
1452
1453        let recovered = source.find("Properties();").expect("recovered constructor");
1454        assert!(
1455            is_declarator_name(&tree, source, recovered, "Properties"),
1456            "a constructor declaration the parse read as a call is still a declarator"
1457        );
1458
1459        for (label, start, text) in [
1460            (
1461                "class name in the recovered header",
1462                source
1463                    .find("class SAMPLE_EXPORT Properties")
1464                    .expect("class")
1465                    + "class SAMPLE_EXPORT ".len(),
1466                "Properties",
1467            ),
1468            (
1469                "macro invocation in the recovered body",
1470                source
1471                    .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1472                    .expect("macro invocation"),
1473                "DISALLOW_COPY_AND_ASSIGN",
1474            ),
1475            (
1476                "call inside a method body the recovery kept",
1477                source.find("return size();").expect("member call") + "return ".len(),
1478                "size",
1479            ),
1480        ] {
1481            assert!(
1482                !is_declarator_name(&tree, source, start, text),
1483                "the {label} at byte {start} stays a reference"
1484            );
1485        }
1486    }
1487
1488    /// Every class role in one file, so a header can be classified with no
1489    /// ordinary body-bearing class in it.
1490    fn class_roles(source: &str, name: &str) -> Vec<CppOccurrenceRole> {
1491        let tree = parse_cpp(source);
1492        let file = ProjectFile::new(std::env::temp_dir(), "occurrence-role.hpp");
1493        let parsed = crate::adapter::parse_cpp_file(&file, source, &tree);
1494        let unit = parsed
1495            .declarations()
1496            .iter()
1497            .find(|unit| unit.is_class() && unit.fq_name() == name)
1498            .unwrap_or_else(|| panic!("missing class {name}: {parsed:#?}"));
1499        let ranges = parsed.declaration_ranges(unit);
1500        assert!(!ranges.is_empty(), "{name} must have a declaration range");
1501        ranges
1502            .iter()
1503            .map(|range| {
1504                cpp_occurrence_role_for_range(
1505                    &CppRecoveredExportClassIndex::build(tree.root_node(), source),
1506                    tree.root_node(),
1507                    source,
1508                    unit,
1509                    range,
1510                )
1511            })
1512            .collect()
1513    }
1514
1515    /// #2557: the role of a recovered `class MACRO(2, 0) Name` comes from the
1516    /// recovery itself. The fallback climb lands on the enclosing container, so
1517    /// a header whose classes are all export-macro decorated used to hold no
1518    /// body-bearing `class_specifier` at all and every class in it read as a
1519    /// forward declaration, which dropped it from navigation.
1520    #[test]
1521    fn recovered_export_macro_classes_are_definitions_without_an_ordinary_class() {
1522        let source = concat!(
1523            "namespace api {\n",
1524            "class PROJECT_PUBLIC_API(2, 0) Name final {\n",
1525            "  public:\n",
1526            "    Name();\n",
1527            "};\n",
1528            "class PROJECT_PUBLIC_API(2, 0) Other : public Name {\n",
1529            "  public:\n",
1530            "    Other();\n",
1531            "};\n",
1532            "} // namespace api\n",
1533        );
1534        for name in ["api.Name", "api.Other"] {
1535            assert_eq!(
1536                class_roles(source, name),
1537                vec![CppOccurrenceRole::Definition],
1538                "{name} is a complete recovered definition"
1539            );
1540        }
1541    }
1542
1543    #[test]
1544    fn ordinary_class_roles_keep_their_plain_specifier_reading() {
1545        assert_eq!(
1546            class_roles("class Plain;\n", "Plain"),
1547            vec![CppOccurrenceRole::DeclarationOnly],
1548            "a forward declaration stays a declaration"
1549        );
1550        assert_eq!(
1551            class_roles("class Plain { };\n", "Plain"),
1552            vec![CppOccurrenceRole::Definition],
1553            "a complete class stays a definition"
1554        );
1555    }
1556}