mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(lsp): name Go init() callers per file in the Go LSP
#1910 gives every Go init() its own QN, pkg.init#<file>:<N>, on the def side and in compute_func_qn. The Go LSP computes its enclosing-function QN on its own and still produced the flat pkg.init. The pipeline joins LSP rows to textual calls by exact caller QN, so every LSP resolution inside an init body missed the join: typed calls fell back to the registry resolver and callable references (Register(fn)) were dropped. On kubernetes (8c078bc3), without this commit all 499 lsp_* CALLS from init bodies fell back (import_map, same_module, unique_name) and all 562 CALL_REFERENCE edges from them were lost; with it, the init edges match main's strategy mix. - helpers.c: cbm_go_init_qn is the one formula for all three sides (def extractor, compute_func_qn, Go LSP process_function). The per-file ordinal now walks the top-level declarations with a cursor: ts_node_named_child(root, i) is O(i) per call, so the indexed loop was quadratic on wide generated files. - go_lsp: GoLSPContext carries the file's rel_path. cbm_run_go_lsp, cbm_run_go_lsp_cross and cbm_run_go_lsp_cross_with_registry take it, so the per-file walk, the shared Tier-2 registry walk and the per-file fallback all name init callers like the def side. Tier 3 copies the per-file rows' caller QN, so it follows. - pass_lsp_cross: the dispatch passes rel to the Tier-2 walk and to the per-file fallback (cbm_pxc_run_one_with_php_bindings gains rel_path; the incremental route can run without shared registries). - compute_func_qn: a NULL base QN keeps the flat QN, as the receiver branch above it does. Tests: golsp_init_caller_qn_matches_def (per-file rows and the caller join), golsp_crossfile_init_caller_qn_per_file (both cross entry points), pipeline_go_init_lsp_edges_per_file and its _parallel twin (three inits in two files: lsp_ CALLS and a CALL_REFERENCE on the right init, no cross-wiring, nothing onto a decoy), and pipeline_go_init_dispatch_fallback_names_init_caller. Each fails with its piece reverted; the pipeline tests also fail with the compute_func_qn mirror disabled, which no test covered before. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
776f6f589a
commit
6e784c38a2
13 files changed
+507
-90
No files matched your search
+1
-1
@@ -3919,7 +3919,7 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
|
||||
uint64_t lsp_start = now_ns();
|
||||
if (!result->lsp_skipped) {
|
||||
if (language == CBM_LANG_GO) {
|
||||
cbm_run_go_lsp(a, result, source, source_len, root);
|
||||
cbm_run_go_lsp(a, result, source, source_len, root, rel_path);
|
||||
}
|
||||
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
|
||||
if (!cbm_run_c_lsp_with_test_owners(a, result, source, source_len, root,
|
||||
|
||||
@@ -7069,14 +7069,13 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
* above the function — the QN is node identity, and a line suffix would
|
||||
* churn nodes on every unrelated insertion. Nothing ever joins on the
|
||||
* plain QN — calling init explicitly is illegal in Go — and the
|
||||
* call-scope side (compute_func_qn in extract_unified.c) computes the
|
||||
* same cbm_go_init_ordinal so init-body calls keep their attribution. */
|
||||
* call-scope side (compute_func_qn in extract_unified.c) and the Go LSP
|
||||
* (process_function in lsp/go_lsp.c) name it through the same
|
||||
* cbm_go_init_qn, so init-body calls keep their attribution. */
|
||||
if (ctx->language == CBM_LANG_GO && strcmp(def.label, "Function") == 0 &&
|
||||
strcmp(name, "init") == 0) {
|
||||
const char *base = strrchr(ctx->rel_path, '/');
|
||||
base = base ? base + 1 : ctx->rel_path;
|
||||
def.qualified_name = cbm_arena_sprintf(a, "%s#%s:%d", def.qualified_name, base,
|
||||
cbm_go_init_ordinal(node, ctx->source));
|
||||
def.qualified_name =
|
||||
cbm_go_init_qn(a, def.qualified_name, ctx->rel_path, node, ctx->source);
|
||||
}
|
||||
|
||||
// C++/CUDA: out-of-line method definition (`Foo::bar` in a .cc/.cpp). The
|
||||
|
||||
@@ -1136,17 +1136,18 @@ static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLan
|
||||
/* Go init(): the def extractor folds the file basename and the init's
|
||||
* per-file ordinal into the QN so every init in a package survives the
|
||||
* upsert (#1910, the #495 cfg-twin pattern; ordinal, not line, so the QN
|
||||
* is stable under edits above the function). Mirror the exact formula
|
||||
* here, or init-body calls fall back to File-node attribution
|
||||
* is stable under edits above the function). Name it through the same
|
||||
* cbm_go_init_qn, or init-body calls fall back to File-node attribution
|
||||
* (calls_find_source). */
|
||||
if (ctx->language == CBM_LANG_GO && strcmp(name, "init") == 0 &&
|
||||
strcmp(ts_node_type(node), "function_declaration") == 0) {
|
||||
const char *base_qn = cbm_fqn_compute_source_lang(ctx->arena, ctx->project, ctx->rel_path,
|
||||
name, ctx->language);
|
||||
const char *base = strrchr(ctx->rel_path, '/');
|
||||
base = base ? base + 1 : ctx->rel_path;
|
||||
return cbm_arena_sprintf(ctx->arena, "%s#%s:%d", base_qn, base,
|
||||
cbm_go_init_ordinal(node, ctx->source));
|
||||
/* NULL only on allocation failure: fall through to the flat QN, as
|
||||
* the receiver branch above does. */
|
||||
if (base_qn) {
|
||||
return cbm_go_init_qn(ctx->arena, base_qn, ctx->rel_path, node, ctx->source);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nix: a binding's own attrpath contributes scope (`a.b.fn = …`), and the def
|
||||
|
||||
+59
-36
@@ -2330,48 +2330,71 @@ int cbm_classify_string(const char *str, int len) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
/* 1-based source-order ordinal of a top-level Go `init` function_declaration
|
||||
* among the file's `init`s. Unlike a line number, the ordinal is stable under
|
||||
* edits above the function — QNs are node identity, and a line-based suffix
|
||||
* would churn nodes on every unrelated insertion (the #495 cfg suffix and the
|
||||
* GoogleTest derivation are both edit-stable for the same reason).
|
||||
* One cursor pass over the top-level declarations: ts_node_named_child(root, i)
|
||||
* is O(i), so an indexed loop is O(n^2) per init on a wide file, and generated
|
||||
* Go (.pb.go, tables) carries thousands of top-level declarations next to its
|
||||
* init(). */
|
||||
static int go_init_ordinal(TSNode fn_node, const char *source) {
|
||||
TSNode root = fn_node;
|
||||
for (TSNode p = ts_node_parent(root); !ts_node_is_null(p); p = ts_node_parent(p)) {
|
||||
root = p;
|
||||
}
|
||||
int ordinal = 0;
|
||||
TSTreeCursor cur = ts_tree_cursor_new(root);
|
||||
if (ts_tree_cursor_goto_first_child(&cur)) {
|
||||
do {
|
||||
TSNode child = ts_tree_cursor_current_node(&cur);
|
||||
if (strcmp(ts_node_type(child), "function_declaration") != 0) {
|
||||
continue;
|
||||
}
|
||||
TSNode name = ts_node_child_by_field_name(child, TS_FIELD("name"));
|
||||
if (ts_node_is_null(name)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t start = ts_node_start_byte(name);
|
||||
if (ts_node_end_byte(name) - start != 4 || memcmp(source + start, "init", 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
ordinal++;
|
||||
if (ts_node_eq(child, fn_node)) {
|
||||
break;
|
||||
}
|
||||
} while (ts_tree_cursor_goto_next_sibling(&cur));
|
||||
}
|
||||
ts_tree_cursor_delete(&cur);
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
/* Qualified name of a Go `init` function (#1910). Go allows any number of
|
||||
* init() per package — even several in one file — so the flat `pkg.init`
|
||||
* collides and the graph upsert keeps one node. The QN folds in the file
|
||||
* basename and the init's per-file ordinal: `pkg.init#file.go:N`. Every side
|
||||
* that names an init calls this one function — the definition extractor, the
|
||||
* call-scope side (compute_func_qn) and the Go LSP's enclosing-function QN —
|
||||
* so the three cannot drift. A NULL base QN or path keeps base_qn as is. */
|
||||
const char *cbm_go_init_qn(CBMArena *a, const char *base_qn, const char *rel_path, TSNode fn_node,
|
||||
const char *source) {
|
||||
if (!base_qn || !rel_path) {
|
||||
return base_qn;
|
||||
}
|
||||
const char *base = strrchr(rel_path, '/');
|
||||
base = base ? base + 1 : rel_path;
|
||||
const char *qn =
|
||||
cbm_arena_sprintf(a, "%s#%s:%d", base_qn, base, go_init_ordinal(fn_node, source));
|
||||
return qn ? qn : base_qn;
|
||||
}
|
||||
|
||||
/* Flatten a JS/TS `template_string` node into plain text (issue #1006).
|
||||
* String fragments are kept verbatim; each ${...} substitution becomes the
|
||||
* "{}" placeholder so client URLs built from template literals share the
|
||||
* canonical parameter shape of server-side route paths
|
||||
* (`/things/${id}/x` -> "/things/{}/x"). Returns NULL when the node yields
|
||||
* no text or exceeds the route-sized buffer. */
|
||||
/* 1-based source-order ordinal of a top-level Go `init` function_declaration
|
||||
* among the file's `init`s. Both the definition extractor and compute_func_qn
|
||||
* fold it into the QN (`pkg.init#file.go:N`, #1910), so it must be computed
|
||||
* from the tree on both sides with this one formula. Unlike a line number,
|
||||
* the ordinal is stable under edits above the function — QNs are node
|
||||
* identity, and a line-based suffix would churn nodes on every unrelated
|
||||
* insertion (the #495 cfg suffix and the GoogleTest derivation are both
|
||||
* edit-stable for the same reason). */
|
||||
int cbm_go_init_ordinal(TSNode fn_node, const char *source) {
|
||||
TSNode root = fn_node;
|
||||
for (TSNode p = ts_node_parent(root); !ts_node_is_null(p); p = ts_node_parent(p)) {
|
||||
root = p;
|
||||
}
|
||||
int ordinal = 0;
|
||||
uint32_t n = ts_node_named_child_count(root);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
TSNode child = ts_node_named_child(root, i);
|
||||
if (strcmp(ts_node_type(child), "function_declaration") != 0) {
|
||||
continue;
|
||||
}
|
||||
TSNode name = ts_node_child_by_field_name(child, TS_FIELD("name"));
|
||||
if (ts_node_is_null(name)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t start = ts_node_start_byte(name);
|
||||
if (ts_node_end_byte(name) - start != 4 || memcmp(source + start, "init", 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
ordinal++;
|
||||
if (ts_node_eq(child, fn_node)) {
|
||||
return ordinal;
|
||||
}
|
||||
}
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
const char *cbm_template_string_text(CBMArena *a, TSNode node, const char *source) {
|
||||
enum { TPL_BUF = 512 };
|
||||
char buf[TPL_BUF];
|
||||
|
||||
@@ -281,16 +281,19 @@ bool cbm_is_loop_node_type(const char *kind);
|
||||
// Is this a module-level node? (not nested inside function/class body)
|
||||
bool cbm_is_module_level(TSNode node, CBMLanguage lang);
|
||||
|
||||
/* 1-based source-order ordinal of a Go `init` among its file's top-level
|
||||
* `init` function_declarations (QN disambiguation, #1910). */
|
||||
int cbm_go_init_ordinal(TSNode fn_node, const char *source);
|
||||
|
||||
// Same check, but the node's PARENT is supplied directly — avoids the
|
||||
// O(n) ts_node_parent rescan. Use at call sites iterating a known
|
||||
// parent's children (the common case). `parent` is the parent of the
|
||||
// node being classified.
|
||||
bool cbm_is_module_level_p(TSNode parent, CBMLanguage lang);
|
||||
|
||||
/* QN of a Go `init` function_declaration (#1910): base_qn + "#<file basename>:<N>",
|
||||
* N = its 1-based ordinal among the file's top-level `init`s. The ONE formula
|
||||
* for the def extractor, compute_func_qn and the Go LSP's enclosing-function
|
||||
* QN. A NULL base_qn or rel_path returns base_qn unchanged. */
|
||||
const char *cbm_go_init_qn(CBMArena *a, const char *base_qn, const char *rel_path, TSNode fn_node,
|
||||
const char *source);
|
||||
|
||||
// --- FQN computation ---
|
||||
|
||||
// Compute qualified name: project.rel_path_parts.name
|
||||
|
||||
+18
-10
@@ -1994,6 +1994,8 @@ static void process_function(GoLSPContext *ctx, TSNode func_node) {
|
||||
// resolver. Since #1909 receiver-qualified Go method QNs, that shared
|
||||
// form is package_qn.ReceiverType.method for methods (the ONE-formula
|
||||
// contract: cbm_go_receiver_type_name) and package_qn.func for functions.
|
||||
// A package may hold any number of init() functions, so each one is
|
||||
// package_qn.init#<file>:<N> (#1910, cbm_go_init_qn — the same formula).
|
||||
ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->package_qn, func_name);
|
||||
if (strcmp(ts_node_type(func_node), "method_declaration") == 0) {
|
||||
TSNode recv = ts_node_child_by_field_name(func_node, "receiver", 8);
|
||||
@@ -2004,6 +2006,9 @@ static void process_function(GoLSPContext *ctx, TSNode func_node) {
|
||||
recv_type, func_name);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(func_name, "init") == 0) {
|
||||
ctx->enclosing_func_qn = cbm_go_init_qn(ctx->arena, ctx->enclosing_func_qn, ctx->rel_path,
|
||||
func_node, ctx->source);
|
||||
}
|
||||
|
||||
// Push function scope
|
||||
@@ -2261,7 +2266,7 @@ static void go_registry_with_stdlib(CBMTypeRegistry *reg, CBMArena *arena) {
|
||||
// --- Entry point: build registry from file defs + run LSP ---
|
||||
|
||||
void cbm_run_go_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root) {
|
||||
TSNode root, const char *rel_path) {
|
||||
|
||||
CBMTypeRegistry reg;
|
||||
cbm_registry_init(®, arena);
|
||||
@@ -2619,6 +2624,7 @@ void cbm_run_go_lsp(CBMArena *arena, CBMFileResult *result, const char *source,
|
||||
// Phase 2: Build LSP context and run
|
||||
GoLSPContext lsp_ctx;
|
||||
go_lsp_init(&lsp_ctx, arena, source, source_len, ®, module_qn, &result->resolved_calls);
|
||||
lsp_ctx.rel_path = rel_path;
|
||||
|
||||
// Add imports
|
||||
for (int i = 0; i < result->imports.count; i++) {
|
||||
@@ -3218,9 +3224,9 @@ static void extract_type_params_from_ast(CBMArena *arena, CBMTypeRegistry *reg,
|
||||
extern const TSLanguage *tree_sitter_go(void);
|
||||
|
||||
void cbm_run_go_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMLSPDef *defs, int def_count,
|
||||
const char **import_names, const char **import_qns, int import_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out) {
|
||||
const char *module_qn, const char *rel_path, CBMLSPDef *defs,
|
||||
int def_count, const char **import_names, const char **import_qns,
|
||||
int import_count, TSTree *cached_tree, CBMResolvedCallArray *out) {
|
||||
if (!source || source_len <= 0)
|
||||
return;
|
||||
|
||||
@@ -3483,6 +3489,7 @@ void cbm_run_go_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
// 4. Build LSP context and run
|
||||
GoLSPContext ctx;
|
||||
go_lsp_init(&ctx, arena, source, source_len, ®, module_qn, out);
|
||||
ctx.rel_path = rel_path;
|
||||
|
||||
for (int i = 0; i < import_count; i++) {
|
||||
if (import_names[i] && import_qns[i]) {
|
||||
@@ -3624,10 +3631,10 @@ CBMTypeRegistry *cbm_go_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, i
|
||||
}
|
||||
|
||||
void cbm_run_go_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMTypeRegistry *reg,
|
||||
const char **import_names, const char **import_qns,
|
||||
int import_count, TSTree *cached_tree,
|
||||
CBMResolvedCallArray *out) {
|
||||
const char *module_qn, const char *rel_path,
|
||||
CBMTypeRegistry *reg, const char **import_names,
|
||||
const char **import_qns, int import_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out) {
|
||||
if (!source || source_len <= 0 || !reg)
|
||||
return;
|
||||
|
||||
@@ -3653,6 +3660,7 @@ void cbm_run_go_lsp_cross_with_registry(CBMArena *arena, const char *source, int
|
||||
|
||||
GoLSPContext ctx;
|
||||
go_lsp_init(&ctx, arena, source, source_len, reg, module_qn, out);
|
||||
ctx.rel_path = rel_path;
|
||||
for (int i = 0; i < import_count; i++) {
|
||||
if (import_names[i] && import_qns[i]) {
|
||||
go_lsp_add_import(&ctx, import_names[i], import_qns[i]);
|
||||
@@ -3802,8 +3810,8 @@ void cbm_batch_go_lsp_cross(CBMArena *arena, CBMBatchGoLSPFile *files, int file_
|
||||
|
||||
// Delegate to existing per-file function
|
||||
cbm_run_go_lsp_cross(&file_arena, file->source, file->source_len, file->module_qn,
|
||||
file->defs, file->def_count, file->import_names, file->import_qns,
|
||||
file->import_count, file->cached_tree, &file_out);
|
||||
file->rel_path, file->defs, file->def_count, file->import_names,
|
||||
file->import_qns, file->import_count, file->cached_tree, &file_out);
|
||||
|
||||
// Copy results to output arena (must outlive per-file arena)
|
||||
if (file_out.count > 0) {
|
||||
|
||||
@@ -22,6 +22,7 @@ typedef struct {
|
||||
// Current function context
|
||||
const char* enclosing_func_qn;
|
||||
const char* package_qn; // module QN for this file
|
||||
const char* rel_path; // repo-relative file path: init() QN suffix (#1910); NULL = flat
|
||||
|
||||
// Output: resolved calls accumulate here
|
||||
CBMResolvedCallArray* resolved_calls;
|
||||
@@ -63,8 +64,10 @@ const CBMRegisteredFunc* go_lookup_field_or_method(GoLSPContext* ctx,
|
||||
|
||||
// Entry point: build registry from file's own defs + run LSP resolution.
|
||||
// Called from cbm_extract_file() after definitions and imports are extracted.
|
||||
// rel_path is the file's repo-relative path: it names the file's init()
|
||||
// functions exactly as the def extractor does (cbm_go_init_qn, #1910).
|
||||
void cbm_run_go_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
const char* source, int source_len, TSNode root);
|
||||
const char* source, int source_len, TSNode root, const char* rel_path);
|
||||
|
||||
// Register Go stdlib types and functions into a registry.
|
||||
// Auto-generated by scripts/gen-go-stdlib.go.
|
||||
@@ -108,11 +111,14 @@ typedef struct {
|
||||
// Re-parses source with tree-sitter internally.
|
||||
// defs includes both file-local and cross-file definitions.
|
||||
// import_names/import_qns are parallel arrays of length import_count.
|
||||
// rel_path: the file's repo-relative path, for its init() QNs (#1910); NULL
|
||||
// leaves init callers flat (they then miss the caller join).
|
||||
// Results written to out (arena-allocated).
|
||||
void cbm_run_go_lsp_cross(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
const char* rel_path,
|
||||
CBMLSPDef* defs, int def_count,
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree, // NULL = parse internally
|
||||
@@ -131,6 +137,7 @@ void cbm_run_go_lsp_cross_with_registry(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
const char* rel_path, // see cbm_run_go_lsp_cross
|
||||
CBMTypeRegistry* reg, // pre-built, finalized, READ-ONLY
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree, // NULL = parse internally
|
||||
@@ -156,6 +163,7 @@ typedef struct {
|
||||
const char* source;
|
||||
int source_len;
|
||||
const char* module_qn;
|
||||
const char* rel_path; // repo-relative path (init() QNs, #1910); NULL = flat
|
||||
TSTree* cached_tree; // from TSTree caching (NULL = parse internally)
|
||||
CBMLSPDef* defs; // combined file-local + cross-file defs
|
||||
int def_count;
|
||||
|
||||
@@ -2079,14 +2079,15 @@ static CBMRustLSPDef *pxc_lspdefs_to_rust(CBMArena *arena, const CBMLSPDef *defs
|
||||
void cbm_pxc_run_one(CBMLanguage lang, CBMFileResult *r, const char *source, int source_len,
|
||||
const char *module_qn, CBMLSPDef *defs, int def_count, const char **imp_names,
|
||||
const char **imp_qns, int imp_count) {
|
||||
cbm_pxc_run_one_with_php_bindings(lang, r, source, source_len, module_qn, defs, def_count,
|
||||
cbm_pxc_run_one_with_php_bindings(lang, r, source, source_len, module_qn, NULL, defs, def_count,
|
||||
imp_names, imp_qns, imp_count, NULL);
|
||||
}
|
||||
|
||||
void cbm_pxc_run_one_with_php_bindings(CBMLanguage lang, CBMFileResult *r, const char *source,
|
||||
int source_len, const char *module_qn, CBMLSPDef *defs,
|
||||
int def_count, const char **imp_names, const char **imp_qns,
|
||||
int imp_count, const CBMPHPImportBindings *bindings) {
|
||||
int source_len, const char *module_qn, const char *rel_path,
|
||||
CBMLSPDef *defs, int def_count, const char **imp_names,
|
||||
const char **imp_qns, int imp_count,
|
||||
const CBMPHPImportBindings *bindings) {
|
||||
TSTree *tree = r->cached_tree; /* may be NULL — LSP re-parses then */
|
||||
|
||||
CBMArena scratch;
|
||||
@@ -2098,8 +2099,8 @@ void cbm_pxc_run_one_with_php_bindings(CBMLanguage lang, CBMFileResult *r, const
|
||||
|
||||
switch (lang) {
|
||||
case CBM_LANG_GO:
|
||||
cbm_run_go_lsp_cross(&scratch, source, source_len, module_qn, defs, def_count, imp_names,
|
||||
imp_qns, imp_count, tree, &out);
|
||||
cbm_run_go_lsp_cross(&scratch, source, source_len, module_qn, rel_path, defs, def_count,
|
||||
imp_names, imp_qns, imp_count, tree, &out);
|
||||
break;
|
||||
case CBM_LANG_C:
|
||||
case CBM_LANG_CPP:
|
||||
@@ -2293,9 +2294,9 @@ void cbm_pxc_dispatch_file_with_php_bindings(
|
||||
* lsp_unresolved entries -- no parse, no AST walk -- then the
|
||||
* AST walk against the overlay (chained to the sealed base). */
|
||||
cbm_go_fast_resolve_qualified_calls(result, prebuilt, imp_keys, imp_vals, imp_count);
|
||||
cbm_run_go_lsp_cross_with_registry(&scratch, source, source_len, def_module, &overlay,
|
||||
imp_keys, imp_vals, imp_count, result->cached_tree,
|
||||
&out);
|
||||
cbm_run_go_lsp_cross_with_registry(&scratch, source, source_len, def_module, rel,
|
||||
&overlay, imp_keys, imp_vals, imp_count,
|
||||
result->cached_tree, &out);
|
||||
used_prebuilt = true;
|
||||
break;
|
||||
case CBM_LANG_PYTHON: {
|
||||
@@ -2429,7 +2430,7 @@ void cbm_pxc_dispatch_file_with_php_bindings(
|
||||
pxc_append_synthetic_calls(&result->arena, &result->calls, &synthetic_calls);
|
||||
pxc_scratch_give(PXC_SCRATCH_DISPATCH, &scratch);
|
||||
} else {
|
||||
cbm_pxc_run_one_with_php_bindings(lang, result, source, source_len, def_module,
|
||||
cbm_pxc_run_one_with_php_bindings(lang, result, source, source_len, def_module, rel,
|
||||
file_defs, file_def_count, imp_keys, imp_vals,
|
||||
imp_count, bindings);
|
||||
}
|
||||
@@ -2441,8 +2442,9 @@ void cbm_pxc_dispatch_file_with_php_bindings(
|
||||
cbm_pxc_run_one_ts(result, source, source_len, def_module, file_defs, file_def_count,
|
||||
imp_keys, imp_vals, imp_count, js, jsx, dts);
|
||||
} else {
|
||||
cbm_pxc_run_one_with_php_bindings(lang, result, source, source_len, def_module, file_defs,
|
||||
file_def_count, imp_keys, imp_vals, imp_count, bindings);
|
||||
cbm_pxc_run_one_with_php_bindings(lang, result, source, source_len, def_module, rel,
|
||||
file_defs, file_def_count, imp_keys, imp_vals, imp_count,
|
||||
bindings);
|
||||
}
|
||||
free(filtered);
|
||||
}
|
||||
|
||||
@@ -216,10 +216,14 @@ void cbm_pxc_run_one(CBMLanguage lang, CBMFileResult *r, const char *source, int
|
||||
const char *module_qn, CBMLSPDef *defs, int def_count, const char **imp_names,
|
||||
const char **imp_qns, int imp_count);
|
||||
|
||||
/* rel_path: the file's repo-relative path. Go needs it to name the file's
|
||||
* init() functions as the def extractor does (#1910); cbm_pxc_run_one passes
|
||||
* NULL (Go init() callers then stay flat). */
|
||||
void cbm_pxc_run_one_with_php_bindings(CBMLanguage lang, CBMFileResult *r, const char *source,
|
||||
int source_len, const char *module_qn, CBMLSPDef *defs,
|
||||
int def_count, const char **imp_names, const char **imp_qns,
|
||||
int imp_count, const CBMPHPImportBindings *bindings);
|
||||
int source_len, const char *module_qn, const char *rel_path,
|
||||
CBMLSPDef *defs, int def_count, const char **imp_names,
|
||||
const char **imp_qns, int imp_count,
|
||||
const CBMPHPImportBindings *bindings);
|
||||
|
||||
/* TS / JS / JSX / TSX variant with explicit dialect flags. */
|
||||
void cbm_pxc_run_one_ts(CBMFileResult *r, const char *source, int source_len, const char *module_qn,
|
||||
|
||||
+2
-2
@@ -16000,8 +16000,8 @@ TEST(seal_go_shared_registry_readonly) {
|
||||
const char *src = "package main\nfunc helper(x int) int { return x + 1 }\n"
|
||||
"func caller() int { return helper(1) }\n";
|
||||
CBMResolvedCallArray out = {0};
|
||||
cbm_run_go_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", reg, NULL, NULL,
|
||||
0, NULL, &out);
|
||||
cbm_run_go_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", NULL, reg, NULL,
|
||||
NULL, 0, NULL, &out);
|
||||
ASSERT_EQ(reg->func_count, fb);
|
||||
ASSERT_EQ(reg->type_count, tb);
|
||||
cbm_arena_destroy(&arena);
|
||||
|
||||
+139
-8
@@ -1063,7 +1063,7 @@ TEST(golsp_crossfile_method_dispatch) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 4, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
ASSERT_GTE(find_resolved_arr(&out, "doQuery", "Connect"), 0);
|
||||
@@ -1109,7 +1109,7 @@ TEST(golsp_crossfile_return_type_chain) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 4, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
ASSERT_GTE(find_resolved_arr(&out, "showUser", "Name"), 0);
|
||||
@@ -1149,7 +1149,7 @@ TEST(golsp_crossfile_interface_dispatch) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 4, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
int idx = find_resolved_arr_confident(&out, "handler", "Bind");
|
||||
@@ -1205,7 +1205,7 @@ TEST(golsp_crossfile_interface_field_chain) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 6, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 6, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
int idx = find_resolved_arr_confident(&out, "process", "Bind");
|
||||
@@ -1245,7 +1245,7 @@ TEST(golsp_crossfile_map_index) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 3, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 3, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
int idx = find_resolved_arr_confident(&out, "dispatch", "ServeHTTP");
|
||||
@@ -1275,7 +1275,7 @@ TEST(golsp_crossfile_stdlib_interface) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 1, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 1, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
ASSERT_GTE(find_resolved_arr_confident(&out, "process", "Done"), 0);
|
||||
@@ -1349,7 +1349,7 @@ TEST(golsp_crossfile_aliased_field_requal) {
|
||||
CBMTypeRegistry *reg = cbm_go_build_cross_registry(&arena, defs, 6);
|
||||
ASSERT_NOT_NULL(reg);
|
||||
|
||||
cbm_run_go_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.main", reg,
|
||||
cbm_run_go_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.main", NULL, reg,
|
||||
imp_names, imp_qns, 2, NULL, &out);
|
||||
|
||||
int svc_idx = find_resolved_arr_confident(&out, "callSvc", "Svc.Ping");
|
||||
@@ -1417,7 +1417,7 @@ TEST(golsp_crossfile_local_interface_single_impl) {
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 5, imp_names,
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", NULL, defs, 5, imp_names,
|
||||
imp_qns, 1, NULL, &out);
|
||||
|
||||
int idxGet = find_resolved_arr_confident(&out, "process", "Get");
|
||||
@@ -1433,6 +1433,133 @@ TEST(golsp_crossfile_local_interface_single_impl) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Go init(): per-file caller QN (#1910) ──────────────────────── */
|
||||
|
||||
/* Every init() of a package is its own node, pkg.init#<file>:<N>
|
||||
* (cbm_go_init_qn). The LSP's enclosing-function QN must be exactly that QN:
|
||||
* the pipeline joins LSP rows to textual calls by caller QN, so a flat
|
||||
* pkg.init loses every LSP resolution inside an init body. Two inits in one
|
||||
* file pin the ordinal; the method name exists on both types, so only the
|
||||
* typed receiver tells the targets apart. */
|
||||
TEST(golsp_init_caller_qn_matches_def) {
|
||||
const char *source = "package main\n\n"
|
||||
"type Codec struct{}\n\n"
|
||||
"func (c *Codec) register() {}\n\n"
|
||||
"type Store struct{}\n\n"
|
||||
"func (s *Store) register() {}\n\n"
|
||||
"func init() {\n\tc := &Codec{}\n\tc.register()\n}\n\n"
|
||||
"func init() {\n\ts := &Store{}\n\ts.register()\n}\n";
|
||||
CBMFileResult *r = extract_go(source);
|
||||
ASSERT_NOT_NULL(r);
|
||||
const char *init1 = NULL;
|
||||
const char *init2 = NULL;
|
||||
for (int i = 0; i < r->defs.count; i++) {
|
||||
const CBMDefinition *d = &r->defs.items[i];
|
||||
if (d->name && strcmp(d->name, "init") == 0) {
|
||||
if (!init1) {
|
||||
init1 = d->qualified_name;
|
||||
} else {
|
||||
init2 = d->qualified_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_NOT_NULL(init1);
|
||||
ASSERT_NOT_NULL(init2);
|
||||
ASSERT_NOT_NULL(strstr(init1, ".init#main.go:1"));
|
||||
ASSERT_NOT_NULL(strstr(init2, ".init#main.go:2"));
|
||||
|
||||
int a = require_resolved(r, "init", "Codec.register");
|
||||
ASSERT_GTE(a, 0);
|
||||
ASSERT_STR_EQ(r->resolved_calls.items[a].caller_qn, init1);
|
||||
int b = require_resolved(r, "init", "Store.register");
|
||||
ASSERT_GTE(b, 0);
|
||||
ASSERT_STR_EQ(r->resolved_calls.items[b].caller_qn, init2);
|
||||
|
||||
/* The join itself: each textual register() call (raw callee text
|
||||
* "c.register" / "s.register") finds its own init's row. */
|
||||
int joined = 0;
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
const CBMCall *c = &r->calls.items[i];
|
||||
if (!c->callee_name || !strstr(c->callee_name, ".register")) {
|
||||
continue;
|
||||
}
|
||||
const CBMResolvedCall *rc = cbm_pipeline_find_lsp_resolution(&r->resolved_calls, c, false);
|
||||
ASSERT_NOT_NULL(rc);
|
||||
ASSERT_STR_EQ(rc->caller_qn, c->enclosing_func_qn);
|
||||
ASSERT_NOT_NULL(
|
||||
strstr(rc->callee_qn, strcmp(c->enclosing_func_qn, init1) == 0 ? "Codec" : "Store"));
|
||||
joined++;
|
||||
}
|
||||
ASSERT_EQ(joined, 2);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Cross-file twin: both cross entry points (per-file defs, and the shared
|
||||
* Tier-2 registry the pipeline uses) name the init caller from rel_path. */
|
||||
TEST(golsp_crossfile_init_caller_qn_per_file) {
|
||||
const char *source = "package reg\n\n"
|
||||
"func init() {\n\tc := NewCodec()\n\tc.Register()\n}\n\n"
|
||||
"func init() {\n\ts := NewStore()\n\ts.Register()\n}\n";
|
||||
CBMLSPDef defs[] = {
|
||||
{.qualified_name = "test.reg.NewCodec",
|
||||
.short_name = "NewCodec",
|
||||
.label = "Function",
|
||||
.def_module_qn = "test.reg",
|
||||
.return_types = "*Codec"},
|
||||
{.qualified_name = "test.reg.Codec",
|
||||
.short_name = "Codec",
|
||||
.label = "Class",
|
||||
.def_module_qn = "test.reg"},
|
||||
{.qualified_name = "test.reg.Codec.Register",
|
||||
.short_name = "Register",
|
||||
.label = "Method",
|
||||
.def_module_qn = "test.reg",
|
||||
.receiver_type = "test.reg.Codec"},
|
||||
{.qualified_name = "test.reg.NewStore",
|
||||
.short_name = "NewStore",
|
||||
.label = "Function",
|
||||
.def_module_qn = "test.reg",
|
||||
.return_types = "*Store"},
|
||||
{.qualified_name = "test.reg.Store",
|
||||
.short_name = "Store",
|
||||
.label = "Class",
|
||||
.def_module_qn = "test.reg"},
|
||||
{.qualified_name = "test.reg.Store.Register",
|
||||
.short_name = "Register",
|
||||
.label = "Method",
|
||||
.def_module_qn = "test.reg",
|
||||
.receiver_type = "test.reg.Store"},
|
||||
};
|
||||
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
CBMResolvedCallArray out = {0};
|
||||
cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.reg", "reg/b.go", defs, 6, NULL,
|
||||
NULL, 0, NULL, &out);
|
||||
int c1 = find_resolved_arr_confident(&out, "init", "Codec.Register");
|
||||
ASSERT_GTE(c1, 0);
|
||||
ASSERT_STR_EQ(out.items[c1].caller_qn, "test.reg.init#b.go:1");
|
||||
int s1 = find_resolved_arr_confident(&out, "init", "Store.Register");
|
||||
ASSERT_GTE(s1, 0);
|
||||
ASSERT_STR_EQ(out.items[s1].caller_qn, "test.reg.init#b.go:2");
|
||||
|
||||
CBMResolvedCallArray shared_out = {0};
|
||||
CBMTypeRegistry *reg = cbm_go_build_cross_registry(&arena, defs, 6);
|
||||
ASSERT_NOT_NULL(reg);
|
||||
cbm_run_go_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.reg", "reg/b.go",
|
||||
reg, NULL, NULL, 0, NULL, &shared_out);
|
||||
int c2 = find_resolved_arr_confident(&shared_out, "init", "Codec.Register");
|
||||
ASSERT_GTE(c2, 0);
|
||||
ASSERT_STR_EQ(shared_out.items[c2].caller_qn, "test.reg.init#b.go:1");
|
||||
int s2 = find_resolved_arr_confident(&shared_out, "init", "Store.Register");
|
||||
ASSERT_GTE(s2, 0);
|
||||
ASSERT_STR_EQ(shared_out.items[s2].caller_qn, "test.reg.init#b.go:2");
|
||||
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Suite ─────────────────────────────────────────────────────── */
|
||||
|
||||
SUITE(go_lsp) {
|
||||
@@ -1545,4 +1672,8 @@ SUITE(go_lsp) {
|
||||
RUN_TEST(golsp_crossfile_map_index);
|
||||
RUN_TEST(golsp_crossfile_stdlib_interface);
|
||||
RUN_TEST(golsp_crossfile_local_interface_single_impl);
|
||||
|
||||
/* Go init(): per-file caller QN (#1910) */
|
||||
RUN_TEST(golsp_init_caller_qn_matches_def);
|
||||
RUN_TEST(golsp_crossfile_init_caller_qn_per_file);
|
||||
}
|
||||
@@ -5476,7 +5476,7 @@ TEST(parallel_php_external_row_replaces_only_its_exact_occurrence) {
|
||||
const char *keys[] = {"LocalRequest"};
|
||||
const char *vals[] = {project_type};
|
||||
cbm_pxc_run_one_with_php_bindings(CBM_LANG_PHP, r, source, (int)strlen(source), "p.main",
|
||||
defs, 2, keys, vals, 1, &bindings);
|
||||
NULL, defs, 2, keys, vals, 1, &bindings);
|
||||
int external_count = 0, project_count = 0;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *row = &r->resolved_calls.items[i];
|
||||
|
||||
@@ -10298,6 +10298,194 @@ TEST(pipeline_go_multi_init_nodes_survive) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Edges of `edge_type` whose source and target QNs end with the given
|
||||
* suffixes (go_calls_between for any edge type, without the properties). */
|
||||
static int go_edges_between(cbm_store_t *s, const char *project, const char *edge_type,
|
||||
const char *caller_suffix, const char *callee_suffix) {
|
||||
cbm_edge_t *edges = NULL;
|
||||
int edge_count = 0;
|
||||
cbm_store_find_edges_by_type(s, project, edge_type, &edges, &edge_count);
|
||||
size_t cs = strlen(caller_suffix);
|
||||
size_t ts = strlen(callee_suffix);
|
||||
int hits = 0;
|
||||
for (int i = 0; i < edge_count; i++) {
|
||||
cbm_node_t src = {0};
|
||||
cbm_node_t tgt = {0};
|
||||
if (cbm_store_find_node_by_id(s, edges[i].source_id, &src) == CBM_STORE_OK &&
|
||||
cbm_store_find_node_by_id(s, edges[i].target_id, &tgt) == CBM_STORE_OK &&
|
||||
src.qualified_name && tgt.qualified_name) {
|
||||
size_t sl = strlen(src.qualified_name);
|
||||
size_t tl = strlen(tgt.qualified_name);
|
||||
if (sl >= cs && tl >= ts && strcmp(src.qualified_name + sl - cs, caller_suffix) == 0 &&
|
||||
strcmp(tgt.qualified_name + tl - ts, callee_suffix) == 0) {
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
cbm_node_free_fields(&src);
|
||||
cbm_node_free_fields(&tgt);
|
||||
}
|
||||
if (edges) {
|
||||
cbm_store_free_edges(edges, edge_count);
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
/* Fixture for the #1910 Go init() LSP-join probes (sequential and parallel
|
||||
* twins). Three init() in two files of one package -- two in b.go, so the
|
||||
* per-file ordinal is pinned too -- each calling a method that only the Go
|
||||
* LSP can bind: the receiver's type comes from a constructor in another
|
||||
* file, and the method name also exists on a second package type and on
|
||||
* other.Decoy, so no short-name fallback lands on the right target. a.go's
|
||||
* init also passes a handler by value (add("a", handleA): a CALL_REFERENCE,
|
||||
* which exists only as an LSP row). pad_files > 0 adds filler Go files past
|
||||
* MIN_FILES_FOR_PARALLEL. */
|
||||
static void write_go_init_lsp_fixture(const char *tmp, int pad_files) {
|
||||
write_temp_file(tmp, "go.mod", "module example.com/fxinit\n\ngo 1.22\n");
|
||||
write_temp_file(tmp, "reg/codec.go",
|
||||
"package reg\n"
|
||||
"\n"
|
||||
"type Codec struct{ n int }\n"
|
||||
"\n"
|
||||
"func NewCodec() *Codec { return &Codec{} }\n"
|
||||
"\n"
|
||||
"func (c *Codec) register() { c.n++ }\n");
|
||||
write_temp_file(tmp, "reg/store.go",
|
||||
"package reg\n"
|
||||
"\n"
|
||||
"type Store struct{ open bool }\n"
|
||||
"\n"
|
||||
"func NewStore() *Store { return &Store{} }\n"
|
||||
"\n"
|
||||
"func (s *Store) register() { s.open = true }\n"
|
||||
"\n"
|
||||
"func (s *Store) warm() {}\n");
|
||||
write_temp_file(tmp, "reg/a.go",
|
||||
"package reg\n"
|
||||
"\n"
|
||||
"var handlers = map[string]func(){}\n"
|
||||
"\n"
|
||||
"func handleA() {}\n"
|
||||
"\n"
|
||||
"func add(name string, fn func()) { handlers[name] = fn }\n"
|
||||
"\n"
|
||||
"func init() {\n"
|
||||
"\tc := NewCodec()\n"
|
||||
"\tc.register()\n"
|
||||
"\tadd(\"a\", handleA)\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "reg/b.go",
|
||||
"package reg\n"
|
||||
"\n"
|
||||
"func init() {\n"
|
||||
"\ts := NewStore()\n"
|
||||
"\ts.register()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func init() {\n"
|
||||
"\ts := NewStore()\n"
|
||||
"\ts.warm()\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "other/other.go",
|
||||
"package other\n"
|
||||
"\n"
|
||||
"type Decoy struct{}\n"
|
||||
"\n"
|
||||
"func (d *Decoy) register() {}\n"
|
||||
"\n"
|
||||
"func (d *Decoy) warm() {}\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "pad/filler%d.go", i);
|
||||
snprintf(body, sizeof(body), "package pad\n\nfunc filler%d() int { return %d }\n", i, i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
}
|
||||
|
||||
/* Each init's LSP-resolved edges start at that init's own node. */
|
||||
static int assert_go_init_lsp_edges(cbm_store_t *s, const char *project) {
|
||||
const char *lsp = "\"strategy\":\"lsp_";
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#a.go:1", ".reg.Codec.register", lsp), 1);
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#b.go:1", ".reg.Store.register", lsp), 1);
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#b.go:2", ".reg.Store.warm", lsp), 1);
|
||||
ASSERT_GTE(go_edges_between(s, project, "CALL_REFERENCE", ".reg.init#a.go:1", ".reg.handleA"),
|
||||
1);
|
||||
/* No cross-wiring between the inits, and nothing onto the decoy. */
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#a.go:1", ".reg.Store.register", NULL), 0);
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#b.go:1", ".reg.Codec.register", NULL), 0);
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#b.go:2", ".reg.Store.register", NULL), 0);
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#a.go:1", ".other.Decoy.register", NULL), 0);
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#b.go:1", ".other.Decoy.register", NULL), 0);
|
||||
ASSERT_EQ(go_calls_between(s, project, ".reg.init#b.go:2", ".other.Decoy.warm", NULL), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* #1910: the def extractor and compute_func_qn name each init
|
||||
* pkg.init#<file>:<N>; the Go LSP must name its caller the same way
|
||||
* (cbm_go_init_qn), or every LSP resolution inside an init body loses the
|
||||
* exact caller-QN join and falls back to the registry resolver. */
|
||||
TEST(pipeline_go_init_lsp_edges_per_file) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_init_lsp_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_go_init_lsp_fixture(tmp, 0);
|
||||
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/go_init_lsp.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(assert_go_init_lsp_edges(s, project), 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Parallel twin: >= MIN_FILES_FOR_PARALLEL (50) files plus CBM_WORKERS=4
|
||||
* take the parallel resolver (resolve_worker's Tier-2 dispatch). */
|
||||
TEST(pipeline_go_init_lsp_edges_per_file_parallel) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_init_lsp_par_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_go_init_lsp_fixture(tmp, 52);
|
||||
|
||||
char *old_workers = getenv("CBM_WORKERS");
|
||||
char *saved = old_workers ? strdup(old_workers) : NULL;
|
||||
cbm_setenv("CBM_WORKERS", "4", 1);
|
||||
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/go_init_lsp_par.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(assert_go_init_lsp_edges(s, project), 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
if (saved) {
|
||||
cbm_setenv("CBM_WORKERS", saved, 1);
|
||||
free(saved);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_WORKERS");
|
||||
}
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Fixture for the #1928 cross-language reference-guard probes (sequential and
|
||||
* parallel twins). pad_files > 0 adds filler files to push the index over the
|
||||
* parallel-pipeline threshold, since USAGE/WRITES/READS have one resolver per
|
||||
@@ -16256,6 +16444,53 @@ TEST(pipeline_python_init_import_probe_failure_falls_back) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1910: with no shared Go registry (the incremental route can pass NULL
|
||||
* cross_registries) the dispatch takes the per-file fallback, which must name
|
||||
* each init() caller from the file path exactly as the def extractor does. */
|
||||
TEST(pipeline_go_init_dispatch_fallback_names_init_caller) {
|
||||
const char *source = "package reg\n\n"
|
||||
"func init() {\n\tc := NewCodec()\n\tc.Register()\n}\n\n"
|
||||
"func init() {\n\tc := NewCodec()\n\tc.Register()\n}\n";
|
||||
CBMLSPDef defs[] = {
|
||||
{.qualified_name = "test.reg.NewCodec",
|
||||
.short_name = "NewCodec",
|
||||
.label = "Function",
|
||||
.def_module_qn = "test.reg",
|
||||
.return_types = "*Codec",
|
||||
.lang = CBM_LANG_GO},
|
||||
{.qualified_name = "test.reg.Codec",
|
||||
.short_name = "Codec",
|
||||
.label = "Class",
|
||||
.def_module_qn = "test.reg",
|
||||
.lang = CBM_LANG_GO},
|
||||
{.qualified_name = "test.reg.Codec.Register",
|
||||
.short_name = "Register",
|
||||
.label = "Method",
|
||||
.def_module_qn = "test.reg",
|
||||
.receiver_type = "test.reg.Codec",
|
||||
.lang = CBM_LANG_GO},
|
||||
};
|
||||
CBMFileResult result = {0};
|
||||
cbm_arena_init(&result.arena);
|
||||
cbm_pxc_dispatch_file(CBM_LANG_GO, &result, source, (int)strlen(source), "reg/b.go", "test.reg",
|
||||
NULL, NULL, defs, 3, NULL, NULL, 0, NULL, NULL);
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
for (int i = 0; i < result.resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &result.resolved_calls.items[i];
|
||||
if (!rc->caller_qn || !rc->callee_qn || rc->confidence <= 0 ||
|
||||
strcmp(rc->callee_qn, "test.reg.Codec.Register") != 0) {
|
||||
continue;
|
||||
}
|
||||
first += strcmp(rc->caller_qn, "test.reg.init#b.go:1") == 0;
|
||||
second += strcmp(rc->caller_qn, "test.reg.init#b.go:2") == 0;
|
||||
}
|
||||
cbm_arena_destroy(&result.arena);
|
||||
ASSERT_EQ(first, 1);
|
||||
ASSERT_EQ(second, 1);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1277: typed instance fields of a Python class imported from another file.
|
||||
* Counts CALLS edges caller -> callee (QN suffixes) whose properties name an
|
||||
* LSP strategy. */
|
||||
@@ -28171,6 +28406,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_python_init_register_scope_failure_parallel);
|
||||
RUN_TEST(pipeline_python_init_import_filter_precedence);
|
||||
RUN_TEST(pipeline_python_init_import_probe_failure_falls_back);
|
||||
RUN_TEST(pipeline_go_init_dispatch_fallback_names_init_caller);
|
||||
RUN_TEST(pipeline_issue1527_python_raw_memo_failure_sequential);
|
||||
RUN_TEST(pipeline_issue1527_python_raw_memo_failure_parallel);
|
||||
RUN_TEST(pipeline_issue1527_python_cross_memo_failure_sequential);
|
||||
@@ -28291,6 +28527,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_go_multi_return_receiver_is_typed);
|
||||
RUN_TEST(pipeline_go_multi_return_receiver_is_typed_parallel);
|
||||
RUN_TEST(pipeline_go_multi_init_nodes_survive);
|
||||
RUN_TEST(pipeline_go_init_lsp_edges_per_file);
|
||||
RUN_TEST(pipeline_go_init_lsp_edges_per_file_parallel);
|
||||
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
|
||||
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c_parallel);
|
||||
RUN_TEST(pipeline_rust_call_never_binds_another_language);
|
||||
|
||||
Reference in new issue
Block a user