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