Files
codebase-memory-mcp/internal/cbm/callable_sig.h
T
Martin Vogel 11867dfa74 feat(identity): callable identity plumbing, no language enabled (#2061)
Overloads that share a base QN (Java/C#/C++/Kotlin/Swift/Scala/ObjC) merge
into one graph node today, and that node collects the CALLS of every
overload. The agreed cure is one signature-qualified callable identity for
all overloading languages:

    callable_qn := base_qn [<tparams>] (params) [cvref]

This change lands the plumbing with every language still at
CBM_CALLABLE_ID_NONE, so the graph is byte-identical; each language is
switched on later in its own change (with its index-format bump).

- internal/cbm/callable_sig.{c,h}: the per-language mode side table
  (NONE|TYPED|LABELED_TYPED|LABELED|ARITY; kept out of CBMLangSpec like
  cbm_string_dispatch_suffixes, since ~160 positional rows would otherwise
  all need the member under -Wmissing-field-initializers), the shared
  builder cbm_callable_sig[_mode] for the def and the call-scope QN, and
  the inverse cbm_qn_callable_base_len[_named]. Suffix contract: no '.',
  '::' or '->'; whitespace-normalized; comments/annotations/attributes
  dropped; qualified types keep their last segment; '->' spelled '=>';
  variadic '[]' (Java) / '~'; C++ top-level cv dropped, arrays decay,
  cvref kept; <= 200 bytes, longer ones keep whole leading entries and end
  in '#<fnv1a64 of the full suffix>)'. The builder is iterative (cursor
  walk, bounded declarator loop) and self-checks against the inverse.
- The inverse is anchored to the node's bare name wherever the name is
  known: real QNs already end in ')' without being callables (a Java field
  whose name is its declarator `X = f()`, make `$(VAR)` targets), and only
  the anchor makes the inverse exact on them.
- Leaf splitters strip a suffix first (no-op today): registry by-name key
  (indexed by the bare name for a suffixed callable), cbm_lsp_bare_segment,
  cbm_pipeline_qn_class_method_tail, pxc_last_component, pxc_qn_leaf,
  go_index_visit_node, collect_prod_defs, sg_qn_prefix_len.
- New unused fields: CBMDefinition.qn_sig_off, CBMResolvedCall.callee_sig
  (relocated by result compaction), CBMRegisteredFunc.sig, CBMLSPDef.sig.
  The lsp_surface codec is unchanged (no format bump in this change).
- get_code_snippet and trace_path gain base-QN tiers
  (cbm_store_find_nodes_by_qn_base) that find nothing until suffixes exist.
- store: cbm_store_find_nodes_by_qn_suffix built its LIKE pattern in a
  512-byte buffer (a long suffix was truncated into a pattern that matched
  nothing) and did not escape '_'/'%' (`my_func` also matched `myXfunc`).
  The pattern is now heap-built, escaped, and matched with ESCAPE '\'.

Proof: full sorted node+edge+lsp_surface dumps are byte-identical before
and after on 7 corpora (this repo, elasticsearch libs, Exposed, dotnet
System.Linq, System.Collections, coreclr/gc, a C corpus).

Refs #2061

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 00:10:39 +02:00

84 lines
4.2 KiB
C++

/*
* callable_sig.h — signature-qualified callable identity (#2061).
*
* A callable's graph identity is
*
* callable_qn := base_qn [<tparams>] (params) [cvref]
*
* so overloads that share a base QN (Java/C#/C++/Kotlin/Swift/Scala/ObjC)
* become distinct nodes instead of merging into one node that collects every
* overload's CALLS edges. The `name` column stays the bare name.
*
* Every language selects a mode (cbm_callable_identity). A language whose
* mode is CBM_CALLABLE_ID_NONE keeps its historical QN byte for byte; the
* suffix is applied only where a mode is enabled, and the SAME builder
* (cbm_callable_sig) serves the definition QN and the call-scope QN so the two
* can never disagree.
*
* Suffix contract (what cbm_qn_callable_base_len inverts):
* - it never contains '.', "::" or "->" — the separators every QN leaf
* splitter uses — so a splitter that first strips the suffix finds the
* same leaf as before;
* - it is whitespace-normalized: one space only between two identifier
* characters ("unsigned long", "const T&"), none elsewhere;
* - type spelling: comments, annotations and attributes are dropped;
* qualified type paths keep their last segment (java.util.List -> List,
* std::vector -> vector); "->" in function types is spelled "=>"; a
* variadic/rest marker is "[]" for Java (the JVM array it is) and "~"
* everywhere else; C++ top-level cv-qualifiers are dropped and array
* parameters decay to pointers, as the language itself does;
* - it is at most CBM_CALLABLE_SIG_MAX bytes. A longer signature keeps the
* leading whole parameter entries that fit and ends in "#<16 hex>)", the
* FNV-1a 64 of the full uncapped suffix, so identity is preserved.
*/
#ifndef CBM_CALLABLE_SIG_H
#define CBM_CALLABLE_SIG_H
#include "cbm.h"
#include "arena.h"
#include "tree_sitter/api.h"
#include <stddef.h>
typedef enum {
CBM_CALLABLE_ID_NONE = 0, /* historical base QN, no suffix */
CBM_CALLABLE_ID_TYPED, /* (int,String) */
CBM_CALLABLE_ID_LABELED_TYPED, /* (_:Data,to:URL) Swift */
CBM_CALLABLE_ID_LABELED, /* (_:style:) ObjC selector */
CBM_CALLABLE_ID_ARITY, /* (2) dynamic langs */
} CBMCallableIdentity;
enum { CBM_CALLABLE_SIG_MAX = 200 };
/* The identity mode a language uses for callable QNs. Every language is
* CBM_CALLABLE_ID_NONE until its enable change lands (with its index-format
* bump). Kept as a side table, like cbm_string_dispatch_suffixes, rather than
* a CBMLangSpec field: the ~160 positional lang_specs rows would all have to
* spell the new member under -Wmissing-field-initializers. */
CBMCallableIdentity cbm_callable_identity(CBMLanguage lang);
/* The identity suffix for the callable at `func_node` in the language's own
* mode, or NULL when that mode is NONE (or the node carries no parameter
* information the mode can use). Arena-owned. */
const char *cbm_callable_sig(CBMArena *a, TSNode func_node, const char *source, CBMLanguage lang);
/* Same, with an explicit mode — the builder the enable changes and the golden
* tables exercise before a language is switched on. */
const char *cbm_callable_sig_mode(CBMArena *a, TSNode func_node, const char *source,
CBMLanguage lang, CBMCallableIdentity mode);
/* Length of the base QN, i.e. `qn` without its callable identity suffix; equal
* to strlen(qn) when there is none. Purely syntactic (the inverse of the
* builder's contract above); a bare C++ `operator()` leaf is recognised as a
* name, not a suffix. NULL -> 0. */
size_t cbm_qn_callable_base_len(const char *qn);
/* The inverse anchored to the callable's bare `name` (the node's name
* column): the suffix is stripped only when the base QN is `name` or ends in
* "." / "::" + `name`. Other QN text can end in ')' as well — a Java field
* whose name is its declarator `X = f()`, a make `$(VAR)` target — and only
* the anchor makes the inverse exact on it. Use this form wherever the name
* is known; NULL name -> the unanchored form. */
size_t cbm_qn_callable_base_len_named(const char *qn, const char *name);
#endif /* CBM_CALLABLE_SIG_H */