mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2703 from DeusData/fix/go-lsp-multi-return
fix(go-lsp): type variables bound by cross-file multi-value and named results
This commit is contained in:
3 files changed
+394
-19
No files matched your search
@@ -33,7 +33,9 @@
|
||||
#include "pipeline/worker_pool.h"
|
||||
#include "yyjson/yyjson.h"
|
||||
|
||||
enum { SURFACE_CODEC_VERSION = 2 };
|
||||
/* 3: Go defs carry their parsed result list `|`-joined in "ret" (F-G8 D1);
|
||||
* a version-2 surface holds the raw result text and must be re-extracted. */
|
||||
enum { SURFACE_CODEC_VERSION = 3 };
|
||||
|
||||
/* Labels the incremental name registry serves that pxc_map_label does NOT
|
||||
* carry into the CBMLSPDef set. Their (name, qn, label) triple must still
|
||||
|
||||
@@ -166,6 +166,163 @@ static const char *pxc_join_pipe(CBMArena *arena, const char *const *items) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ── Go result lists for the cross-file registrar ─────────────────
|
||||
*
|
||||
* The Go registrar reads a def's return_types as '|'-separated type texts and
|
||||
* parses each piece on its own (go_lsp.c split_pipe_types ->
|
||||
* cbm_parse_return_type_text: `*T`, `[]T`, builtins, named types). A single
|
||||
* result already is one such text. A result LIST arrives as its raw source,
|
||||
* `(*T, error)` or the named `(t *T, err error)`, which parses as nothing, so
|
||||
* pxc_go_result_list splits it into its element types. The extractor's
|
||||
* return_types are no substitute: clean_type_name strips `[]` and `*` for
|
||||
* type-reference edges, and `[]T` read as `T` mistypes `xs[0].M()`. */
|
||||
|
||||
enum { PXC_GO_MAX_RESULTS = 16 };
|
||||
|
||||
typedef struct {
|
||||
const char *text;
|
||||
size_t len;
|
||||
} pxc_span_t;
|
||||
|
||||
static bool pxc_is_space(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
static bool pxc_is_ident_char(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
static int pxc_go_depth(char c, int depth) {
|
||||
if (c == '(' || c == '[' || c == '{') {
|
||||
return depth + 1;
|
||||
}
|
||||
if ((c == ')' || c == ']' || c == '}') && depth > 0) {
|
||||
return depth - 1;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
static pxc_span_t pxc_trim(const char *s, size_t n) {
|
||||
while (n > 0 && pxc_is_space(*s)) {
|
||||
s++;
|
||||
n--;
|
||||
}
|
||||
while (n > 0 && pxc_is_space(s[n - 1])) {
|
||||
n--;
|
||||
}
|
||||
return (pxc_span_t){s, n};
|
||||
}
|
||||
|
||||
/* Split `(a, b)` at its top-level commas into trimmed elements. A trailing
|
||||
* comma (a multi-line list) leaves no element. Returns the element count, or
|
||||
* -1 for text that is no result list or holds an empty element. */
|
||||
static int pxc_go_split_results(const char *raw, pxc_span_t *out) {
|
||||
size_t n = strlen(raw);
|
||||
if (n < 2 || raw[0] != '(' || raw[n - 1] != ')') {
|
||||
return -1;
|
||||
}
|
||||
const char *s = raw + 1;
|
||||
size_t len = n - 2;
|
||||
int count = 0;
|
||||
int depth = 0;
|
||||
size_t start = 0;
|
||||
for (size_t i = 0; i <= len; i++) {
|
||||
bool end = i == len;
|
||||
if (!end && (s[i] != ',' || depth != 0)) {
|
||||
depth = pxc_go_depth(s[i], depth);
|
||||
continue;
|
||||
}
|
||||
pxc_span_t e = pxc_trim(s + start, i - start);
|
||||
if (e.len == 0 && !(end && count > 0)) {
|
||||
return -1;
|
||||
}
|
||||
if (e.len > 0) {
|
||||
if (count == PXC_GO_MAX_RESULTS) {
|
||||
return -1;
|
||||
}
|
||||
out[count++] = e;
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Length of the result NAME that starts an element (`err` in `err error`),
|
||||
* 0 when the element is a bare type. A type keyword is never a name: `chan T`
|
||||
* and `func() T` are types. *lone is set when the whole element is one
|
||||
* identifier: a type in `(T, error)`, a grouped name in `(a, b int)`. */
|
||||
static size_t pxc_go_result_name(pxc_span_t e, bool *lone) {
|
||||
size_t k = 0;
|
||||
while (k < e.len && pxc_is_ident_char(e.text[k])) {
|
||||
k++;
|
||||
}
|
||||
*lone = k > 0 && k == e.len;
|
||||
if (k == 0 || k == e.len || !pxc_is_space(e.text[k])) {
|
||||
return 0;
|
||||
}
|
||||
static const char *const type_keywords[] = {"chan", "func", "map", "struct", "interface", NULL};
|
||||
for (int i = 0; type_keywords[i]; i++) {
|
||||
if (strlen(type_keywords[i]) == k && memcmp(e.text, type_keywords[i], k) == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
/* Named results: each lone name takes the type of the next `name type`
|
||||
* element, as Go groups them. Returns count, or -1 when the list is not a
|
||||
* well-formed named list. */
|
||||
static int pxc_go_named_types(CBMArena *arena, const pxc_span_t *el, int count,
|
||||
const char **types) {
|
||||
int pending = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
bool lone = false;
|
||||
size_t k = pxc_go_result_name(el[i], &lone);
|
||||
if (k == 0) {
|
||||
if (!lone) {
|
||||
return -1;
|
||||
}
|
||||
pending++;
|
||||
continue;
|
||||
}
|
||||
pxc_span_t t = pxc_trim(el[i].text + k, el[i].len - k);
|
||||
const char *type = cbm_arena_sprintf(arena, "%.*s", (int)t.len, t.text);
|
||||
for (int j = i - pending; j <= i; j++) {
|
||||
types[j] = type;
|
||||
}
|
||||
pending = 0;
|
||||
}
|
||||
return pending == 0 ? count : -1;
|
||||
}
|
||||
|
||||
/* `(*T, error)` -> `*T|error`; `(t *T, err error)` -> `*T|error`;
|
||||
* `(a, b int)` -> `int|int`. NULL when raw is no result list (a single
|
||||
* result) or does not split cleanly: the caller keeps the raw text then. */
|
||||
static const char *pxc_go_result_list(CBMArena *arena, const char *raw) {
|
||||
pxc_span_t el[PXC_GO_MAX_RESULTS];
|
||||
int count = pxc_go_split_results(raw, el);
|
||||
if (count <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
bool named = false;
|
||||
for (int i = 0; i < count && !named; i++) {
|
||||
bool lone = false;
|
||||
named = pxc_go_result_name(el[i], &lone) > 0;
|
||||
}
|
||||
const char *types[PXC_GO_MAX_RESULTS + 1];
|
||||
if (named) {
|
||||
if (pxc_go_named_types(arena, el, count, types) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
types[i] = cbm_arena_sprintf(arena, "%.*s", (int)el[i].len, el[i].text);
|
||||
}
|
||||
}
|
||||
types[count] = NULL;
|
||||
return pxc_join_pipe(arena, types);
|
||||
}
|
||||
|
||||
/* ── Cross-file base-class QN resolution ──────────────────────────
|
||||
*
|
||||
* CBMDefinition.base_classes carries the SOURCE SPELLING of each base
|
||||
@@ -520,10 +677,21 @@ static int pxc_build_lsp_def(CBMArena *arena, const CBMDefinition *src, const ch
|
||||
src->decl_namespace ? cbm_arena_strdup(arena, src->decl_namespace) : NULL;
|
||||
}
|
||||
dst->is_interface = (strcmp(label, "Interface") == 0 || strcmp(label, "Protocol") == 0);
|
||||
/* Single return-type string. The per-language registrars split on '|'
|
||||
* for multi-return languages (Go); single-return languages just see one
|
||||
* piece, which is what's already stored. */
|
||||
dst->return_types = src->return_type ? cbm_arena_strdup(arena, src->return_type) : NULL;
|
||||
/* The return-type string. The Go registrar splits it on '|' (go_lsp.c
|
||||
* split_pipe_types), so a Go result LIST arrives split into its element
|
||||
* types: `(*T, error)` as `*T|error`, a named `(t *T)` as `*T`. The raw
|
||||
* list text did not parse, so every variable bound by a cross-file
|
||||
* multi-value or named-result call (`t, err := pkg.New()`) stayed untyped
|
||||
* (F-G8 D1). A single result, and every other language, keeps the raw
|
||||
* text the registrar already parses (`[]T` stays a slice). */
|
||||
const char *go_list = (lang == CBM_LANG_GO && src->return_type)
|
||||
? pxc_go_result_list(arena, src->return_type)
|
||||
: NULL;
|
||||
if (go_list) {
|
||||
dst->return_types = go_list;
|
||||
} else {
|
||||
dst->return_types = src->return_type ? cbm_arena_strdup(arena, src->return_type) : NULL;
|
||||
}
|
||||
/* Languages whose cross registrars read embedded_types as QNs get their
|
||||
* bases resolved against the project registry; everyone else keeps the
|
||||
* raw source spelling their own registrar already knows how to handle. */
|
||||
|
||||
+219
-14
@@ -8819,6 +8819,208 @@ static void write_go_c_ref_guard_fixture(const char *tmp, int pad_files) {
|
||||
}
|
||||
}
|
||||
|
||||
/* F-G8 D1: a variable bound by a CROSS-file multi-value or named-result call
|
||||
* is typed. The cross-file registrar received the raw result text
|
||||
* `(*Encoder, error)`, which did not parse, so `e, err := enc.NewEncoder()`
|
||||
* left e untyped and e.EncodeTuple() fell to a name guess. A single result
|
||||
* keeps its raw text: `[]Sample` must stay a slice so that `xs[0].TS()` is
|
||||
* typed (the extractor's cleaned return_types read it as `Sample`). Every
|
||||
* target method has two same-named decoys, so a guess cannot pass.
|
||||
* pad_files > 0 forces the parallel resolver. */
|
||||
static void write_go_multi_return_fixture(const char *tmp, int pad_files) {
|
||||
write_temp_file(tmp, "go.mod", "module example.com/fx\n\ngo 1.22\n");
|
||||
write_temp_file(tmp, "enc/enc.go",
|
||||
"package enc\n"
|
||||
"\n"
|
||||
"type Encoder struct{ n int }\n"
|
||||
"\n"
|
||||
"func NewEncoder() (*Encoder, error) { return &Encoder{}, nil }\n"
|
||||
"\n"
|
||||
"func NewNamed() (e *Encoder) { e = &Encoder{}; return }\n"
|
||||
"\n"
|
||||
"func NewPair() (a, b *Encoder) { return &Encoder{}, &Encoder{} }\n"
|
||||
"\n"
|
||||
"func (e *Encoder) EncodeTuple() int { return e.n }\n"
|
||||
"\n"
|
||||
"func (e *Encoder) EncodeNamed() int { return e.n }\n"
|
||||
"\n"
|
||||
"func (e *Encoder) EncodeGrouped() int { return e.n }\n");
|
||||
write_temp_file(tmp, "samples/samples.go",
|
||||
"package samples\n"
|
||||
"\n"
|
||||
"type Sample interface{ TS() int64 }\n"
|
||||
"\n"
|
||||
"type sample struct{}\n"
|
||||
"\n"
|
||||
"func (sample) TS() int64 { return 0 }\n"
|
||||
"\n"
|
||||
"func Generate() []Sample { return []Sample{sample{}} }\n"
|
||||
"\n"
|
||||
"func List() ([]Sample, error) { return []Sample{sample{}}, nil }\n");
|
||||
write_temp_file(tmp, "diag/diag.go",
|
||||
"package diag\n"
|
||||
"\n"
|
||||
"type Diagnostic struct{ msg string }\n"
|
||||
"\n"
|
||||
"type Diagnostics []Diagnostic\n"
|
||||
"\n"
|
||||
"func (d Diagnostics) HasErrorsSlice() bool { return len(d) > 0 }\n"
|
||||
"\n"
|
||||
"func Run() (int, Diagnostics) { return 0, nil }\n");
|
||||
write_temp_file(tmp, "app/types.go",
|
||||
"package app\n"
|
||||
"\n"
|
||||
"type isolation struct{}\n"
|
||||
"\n"
|
||||
"func (i *isolation) LowWatermarkField() int { return 0 }\n"
|
||||
"\n"
|
||||
"type Head struct{ iso *isolation }\n"
|
||||
"\n"
|
||||
"func newHead() (*Head, error) { return &Head{iso: &isolation{}}, nil }\n");
|
||||
write_temp_file(tmp, "app/app.go",
|
||||
"package app\n"
|
||||
"\n"
|
||||
"import (\n"
|
||||
"\t\"example.com/fx/diag\"\n"
|
||||
"\t\"example.com/fx/enc\"\n"
|
||||
"\t\"example.com/fx/samples\"\n"
|
||||
")\n"
|
||||
"\n"
|
||||
"func UseIndex() int64 {\n"
|
||||
"\txs := samples.Generate()\n"
|
||||
"\treturn xs[0].TS()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseTupleSlice() int64 {\n"
|
||||
"\tys, _ := samples.List()\n"
|
||||
"\treturn ys[0].TS()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseGrouped() int {\n"
|
||||
"\tx, _ := enc.NewPair()\n"
|
||||
"\treturn x.EncodeGrouped()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseTuple() int {\n"
|
||||
"\te, err := enc.NewEncoder()\n"
|
||||
"\t_ = err\n"
|
||||
"\treturn e.EncodeTuple()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseNamed() int {\n"
|
||||
"\te := enc.NewNamed()\n"
|
||||
"\treturn e.EncodeNamed()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseField() int {\n"
|
||||
"\th, _ := newHead()\n"
|
||||
"\treturn h.iso.LowWatermarkField()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func UseSlice() bool {\n"
|
||||
"\t_, d := diag.Run()\n"
|
||||
"\treturn d.HasErrorsSlice()\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "decoy/decoy.go",
|
||||
"package decoy\n"
|
||||
"\n"
|
||||
"type D1 struct{}\n"
|
||||
"\n"
|
||||
"type D2 struct{}\n"
|
||||
"\n"
|
||||
"func (D1) EncodeTuple() int { return 0 }\n"
|
||||
"func (D2) EncodeTuple() int { return 0 }\n"
|
||||
"func (D1) EncodeNamed() int { return 0 }\n"
|
||||
"func (D2) EncodeNamed() int { return 0 }\n"
|
||||
"func (D1) LowWatermarkField() int { return 0 }\n"
|
||||
"func (D2) LowWatermarkField() int { return 0 }\n"
|
||||
"func (D1) HasErrorsSlice() bool { return false }\n"
|
||||
"func (D2) HasErrorsSlice() bool { return false }\n"
|
||||
"func (D1) EncodeGrouped() int { return 0 }\n"
|
||||
"func (D2) EncodeGrouped() int { return 0 }\n"
|
||||
"func (D1) TS() int64 { return 0 }\n"
|
||||
"func (D2) TS() int64 { return 0 }\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "filler/filler%d.go", i);
|
||||
snprintf(body, sizeof(body), "package filler\n\nfunc Filler%d() int { return %d }\n", i, i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
}
|
||||
|
||||
static int go_multi_return_case(int pad_files) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_multiret_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_go_multi_return_fixture(tmp, pad_files);
|
||||
char *saved = NULL;
|
||||
if (pad_files > 0) {
|
||||
char *old_workers = getenv("CBM_WORKERS");
|
||||
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_multiret.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
int rc = p ? cbm_pipeline_run(p) : -1;
|
||||
if (pad_files > 0) {
|
||||
if (saved) {
|
||||
cbm_setenv("CBM_WORKERS", saved, 1);
|
||||
free(saved);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_WORKERS");
|
||||
}
|
||||
}
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(rc, 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
static const struct {
|
||||
const char *caller;
|
||||
const char *method;
|
||||
const char *file;
|
||||
} want[] = {
|
||||
{"UseTuple", "EncodeTuple", "enc/enc.go"}, /* e, err := enc.NewEncoder() */
|
||||
{"UseNamed", "EncodeNamed", "enc/enc.go"}, /* named result (e *Encoder) */
|
||||
{"UseField", "LowWatermarkField", "app/types.go"}, /* h, _ := newHead(); h.iso.X() */
|
||||
{"UseSlice", "HasErrorsSlice", "diag/diag.go"}, /* _, d := diag.Run() */
|
||||
{"UseGrouped", "EncodeGrouped", "enc/enc.go"}, /* (a, b *Encoder) */
|
||||
{"UseIndex", "TS", "samples/samples.go"}, /* single []Sample, xs[0] */
|
||||
{"UseTupleSlice", "TS", "samples/samples.go"}, /* ([]Sample, error) */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(want) / sizeof(want[0]); i++) {
|
||||
int right = named_edge_to_file_count(s, project, "CALLS", want[i].caller, want[i].method,
|
||||
want[i].file);
|
||||
int decoy = named_edge_to_file_count(s, project, "CALLS", want[i].caller, want[i].method,
|
||||
"decoy/decoy.go");
|
||||
bool typed = cross_file_call_has_strategy(s, project, want[i].caller, want[i].method,
|
||||
"\"strategy\":\"lsp_");
|
||||
if (right < 1 || decoy != 0 || !typed) {
|
||||
printf(" %s -> %s: right=%d decoy=%d lsp=%d\n", want[i].caller, want[i].method, right,
|
||||
decoy, typed);
|
||||
FAIL("multi-value / named-result receiver not typed");
|
||||
}
|
||||
}
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_go_multi_return_receiver_is_typed) {
|
||||
return go_multi_return_case(0);
|
||||
}
|
||||
|
||||
TEST(pipeline_go_multi_return_receiver_is_typed_parallel) {
|
||||
return go_multi_return_case(52);
|
||||
}
|
||||
|
||||
TEST(pipeline_go_rw_usage_never_cross_into_c) {
|
||||
/* #1928: USAGE and WRITES/READS resolve through the same short-name
|
||||
* registry as CALLS but never consulted the #725 cross-language guard.
|
||||
@@ -22881,7 +23083,7 @@ TEST(pipeline_python_namespace_change_admission) {
|
||||
}
|
||||
int body = -9, signature = -9, alias = -9, order = -9;
|
||||
int empty = -9, literal = -9, unknown = -9, deletion = -9, transition = -9;
|
||||
const char *non_python = "{\"v\":2,\"lang\":0,\"lsp\":[],\"reg\":[],\"py\":null}";
|
||||
const char *non_python = "{\"v\":3,\"lang\":0,\"lsp\":[],\"reg\":[],\"py\":null}";
|
||||
if (complete) {
|
||||
body = cbm_lsp_surface_python_change(json[0], json[1]);
|
||||
signature = cbm_lsp_surface_python_change(json[0], json[2]);
|
||||
@@ -23008,22 +23210,22 @@ TEST(pipeline_python_namespace_codec_rejects_non_authoritative_rows) {
|
||||
* Even an empty LSP array must validate its namespace envelope. */
|
||||
const char *bad[] = {
|
||||
"{\"v\":1,\"lsp\":[]}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[]}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":2,\"lang\":1.0,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":2,\"v\":2,\"lang\":0,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[]}",
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":3,\"lang\":1.0,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":3,\"v\":3,\"lang\":0,\"lsp\":[],\"py\":null}",
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":3,\"failure\":2,\"events\":[]}}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":{\"v\":99,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":99,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":[]}}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\\u0000suffix\",\"status\":2,"
|
||||
"\"failure\":0,\"events\":[]}}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
|
||||
"{\"k\":999,\"d\":0,\"s\":0,\"r\":0,\"f\":0,\"l\":0,"
|
||||
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}",
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
|
||||
"{\"k\":8,\"d\":0,\"s\":1,\"r\":0,\"f\":0,\"l\":4294967296,"
|
||||
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}",
|
||||
@@ -23043,7 +23245,7 @@ TEST(pipeline_python_namespace_codec_rejects_non_authoritative_rows) {
|
||||
cbm_arena_destroy(&arena);
|
||||
}
|
||||
const char *unknown =
|
||||
"{\"v\":2,\"lang\":1,\"lsp\":[],\"reg\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"{\"v\":3,\"lang\":1,\"lsp\":[],\"reg\":[],\"py\":{\"v\":1,\"lang\":1,"
|
||||
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
|
||||
"{\"k\":7,\"d\":0,\"s\":0,\"r\":7,\"f\":0,\"l\":0,"
|
||||
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}";
|
||||
@@ -23070,7 +23272,7 @@ TEST(pipeline_lsp_surface_large_object_array_decode) {
|
||||
const size_t capacity = (size_t)count * 192 + 64;
|
||||
char *json = malloc(capacity);
|
||||
ASSERT_NOT_NULL(json);
|
||||
size_t used = (size_t)snprintf(json, capacity, "{\"v\":2,\"lang\":0,\"py\":null,\"lsp\":[");
|
||||
size_t used = (size_t)snprintf(json, capacity, "{\"v\":3,\"lang\":0,\"py\":null,\"lsp\":[");
|
||||
for (int i = 0; i < count; i++) {
|
||||
int n = snprintf(json + used, capacity - used,
|
||||
"%s{\"qn\":\"pkg.f%d\",\"sn\":\"f%d\",\"lb\":\"Function\","
|
||||
@@ -23160,9 +23362,10 @@ TEST(pipeline_lsp_surface_persisted_and_body_edit_invariant) {
|
||||
ASSERT_TRUE(row_a->surface_sha && row_a->surface_sha[0]);
|
||||
if (round == 0) {
|
||||
snprintf(sha_baseline, sizeof(sha_baseline), "%s", row_a->surface_sha);
|
||||
/* The row is the versioned codec envelope with the def present. */
|
||||
/* The row is the versioned codec envelope (codec version 3) with
|
||||
* the def present. */
|
||||
snprintf(json_probe, sizeof(json_probe), "%.20s", row_a->defs_json);
|
||||
ASSERT_TRUE(strstr(row_a->defs_json, "\"v\":2") != NULL);
|
||||
ASSERT_TRUE(strstr(row_a->defs_json, "\"v\":3") != NULL);
|
||||
ASSERT_TRUE(strstr(row_a->defs_json, "surface_probe") != NULL);
|
||||
} else if (round == 1) {
|
||||
ASSERT_STR_EQ(row_a->surface_sha, sha_baseline);
|
||||
@@ -25723,6 +25926,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_python_receiver_suppresses_weak_method_edge);
|
||||
RUN_TEST(pipeline_python_receiver_keeps_specific_unique_name_member_call);
|
||||
RUN_TEST(pipeline_html_embedded_member_call_stays_unbound);
|
||||
RUN_TEST(pipeline_go_multi_return_receiver_is_typed);
|
||||
RUN_TEST(pipeline_go_multi_return_receiver_is_typed_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