Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/cbm/cbm.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ typedef struct {
* change of all, store_impact.h follow_variant_keys). NULL on every other
* definition. Tail field: zero-init stays valid. */
const char *variant_key;
/* Go only (#2033): the raw aliased type text of an identity alias
* `type A = B` ("B"). Carried to the cross-file registries so a method
* call on A from another file follows the alias to B's method set. NULL
* for a defined type `type A B` (its method set is its own) and in every
* other language. Tail field: zero-init stays valid. */
const char *alias_target;
} CBMDefinition;

/* Argument captured from a call expression */
Expand Down
9 changes: 9 additions & 0 deletions internal/cbm/extract_defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -8299,6 +8299,15 @@ static void emit_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *s
def.decl_namespace = cs_type_decl_namespace(a, node, ctx->source);
}
def.base_classes = extract_base_classes(a, node, ctx->source, ctx->language);
// Go identity alias `type A = B` (#2033): keep the aliased type text so the
// cross-file registries can set alias_of. A defined type `type A B` is a
// type_spec and never gets here with this kind.
if (ctx->language == CBM_LANG_GO && strcmp(kind, "type_alias") == 0) {
TSNode target = ts_node_child_by_field_name(node, TS_FIELD("type"));
if (!ts_node_is_null(target)) {
def.alias_target = cbm_node_text(a, target, ctx->source);
}
}
def.decorators = extract_decorators(a, node, ctx->source, ctx->language, spec);
def.docstring = extract_docstring(ctx, node, name);

Expand Down
23 changes: 23 additions & 0 deletions internal/cbm/lsp/go_lsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2705,6 +2705,27 @@ static const char **split_pipe_strings(CBMArena *a, const char *text) {
return idx > 0 ? arr : NULL;
}

/* QN of the type an identity alias `type A = B` names, for the def-driven
* cross-file registries (#2033). The per-file AST scan sets alias_of only for
* the file it walks, so an alias declared in another file of the package
* reaches a call only through here. Only a bare identifier is qualified (B
* declared in A's package); a qualified, pointer, composite or generic target
* stays NULL rather than guessed. */
static const char *go_alias_target_qn(CBMArena *arena, const char *target,
const char *def_module_qn) {
if (!target || !target[0] || !def_module_qn || !def_module_qn[0]) {
return NULL;
}
for (const unsigned char *p = (const unsigned char *)target; *p; p++) {
bool ident = (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
(*p >= '0' && *p <= '9') || *p == '_' || *p >= 0x80;
if (!ident) {
return NULL;
}
}
return cbm_arena_sprintf(arena, "%s.%s", def_module_qn, target);
}

// Helper: parse "|"-separated "name:type" field definitions and populate a registered type.
// Format: "Binder:Binder|Name:string|Count:int"
// type_text is resolved relative to def_module_qn.
Expand Down Expand Up @@ -3273,6 +3294,7 @@ void cbm_run_go_lsp_cross(CBMArena *arena, const char *source, int source_len,
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
rt.method_names = split_pipe_strings(arena, d->method_names_str);
}
rt.alias_of = go_alias_target_qn(arena, d->alias_target, def_mod);

cbm_registry_add_type(&reg, rt);

Expand Down Expand Up @@ -3569,6 +3591,7 @@ CBMTypeRegistry *cbm_go_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, i
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
rt.method_names = split_pipe_strings(arena, d->method_names_str);
}
rt.alias_of = go_alias_target_qn(arena, d->alias_target, def_mod);
cbm_registry_add_type(reg, rt);
GO_NOTE_NEW_TYPES();
if (d->field_defs && d->field_defs[0]) {
Expand Down
4 changes: 4 additions & 0 deletions internal/cbm/lsp/go_lsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ typedef struct {
/* Callable identity (#2061): signature suffix of this def's graph QN;
* qualified_name stays the base QN. NULL = none (always, until enabled). */
const char *sig;
/* Go only (#2033): raw aliased type text of `type A = B` ("B"), qualified
* by the registrar against def_module_qn like return_types. NULL = not an
* identity alias. */
const char *alias_target;
} CBMLSPDef;

// Parse source, build registry from defs + stdlib, run LSP.
Expand Down
1 change: 1 addition & 0 deletions internal/cbm/result_compact.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ static void cr_walk_def(cr_ctx_t *c, CBMDefinition *d) {
cr_list(c, &d->extra_props);
cr_str(c, &d->decl_namespace);
cr_str(c, &d->variant_key);
cr_str(c, &d->alias_target);
}

static void cr_walk_call(cr_ctx_t *c, CBMCall *call) {
Expand Down
8 changes: 6 additions & 2 deletions src/pipeline/lsp_surface.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
#include "yyjson/yyjson.h"

/* 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 };
* a version-2 surface holds the raw result text and must be re-extracted.
* 4: Go identity aliases carry their target in "al" (#2033); a version-3
* surface has no alias targets and must be re-extracted. */
enum { SURFACE_CODEC_VERSION = 4 };

/* Labels the incremental name registry serves that pxc_map_label does NOT
* carry into the CBMLSPDef set. Their (name, qn, label) triple must still
Expand Down Expand Up @@ -394,6 +396,7 @@ static char *surface_file_to_json(const CBMFileResult *result, CBMLanguage langu
yyjson_mut_obj_add_bool(doc, o, "ir", d->is_rust_impl_relation);
yyjson_mut_obj_add_bool(doc, o, "ab", d->is_abstract);
add_str_array_or_null(doc, o, "dec", d->decorators, -1);
add_str_or_null(doc, o, "al", d->alias_target);
yyjson_mut_arr_add_val(lsp, o);
}
yyjson_mut_obj_add_val(doc, root, "lsp", lsp);
Expand Down Expand Up @@ -670,6 +673,7 @@ int cbm_lsp_surface_defs_from_json(CBMArena *arena, const char *defs_json, CBMLS
d->is_abstract = yyjson_get_bool(yyjson_obj_get(o, "ab"));
int dec_count = 0;
d->decorators = arena_str_array(arena, yyjson_obj_get(o, "dec"), true, &dec_count);
d->alias_target = arena_str_or_null(arena, yyjson_obj_get(o, "al"));
if (!d->qualified_name || !d->short_name || !d->label) {
/* A def the writer could not have produced: qn/sn/label are
* unconditionally present in build_lsp_def output. Corrupt row. */
Expand Down
3 changes: 3 additions & 0 deletions src/pipeline/pass_lsp_cross.c
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ static int pxc_build_lsp_def(CBMArena *arena, const CBMDefinition *src, const ch
}
}
dst->lang = lang;
dst->alias_target = (lang == CBM_LANG_GO && src->alias_target)
? cbm_arena_strdup(arena, src->alias_target)
: NULL;
dst->decorators = NULL;
if (src->decorators) {
int n = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/pipeline/pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -2559,7 +2559,7 @@ static void log_result_census(const char *tag, CBMFileResult **cache, int file_c
str_def_misc += census_len(def->route_path) + census_len(def->route_method) +
census_len(def->impl_trait) + census_len(def->http_base_url) +
census_list(def->extra_props) + census_len(def->decl_namespace) +
census_len(def->variant_key);
census_len(def->variant_key) + census_len(def->alias_target);
}
for (int c = 0; c < r->calls.count; c++) {
const CBMCall *call = &r->calls.items[c];
Expand Down
123 changes: 123 additions & 0 deletions tests/test_go_lsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,127 @@ TEST(golsp_crossfile_aliased_field_requal) {
PASS();
}

/* #2033: an identity alias declared in another file of the same package.
* `type FeatureSet = bitmap` lives in one file, `bitmap.Set` in a second, the
* call `reqs.Set` in a third, so none of it is in the calling file's AST and
* only the defs can carry the alias. A defined type (`type Defined bitmap`)
* must not inherit bitmap's methods. The decoy `other.Set` keeps a name-only
* match from passing. Both cross-file registries are checked: the per-file
* one and the prebuilt one shared by the parallel resolve. */
static CBMLSPDef go_crossfile_alias_defs[] = {
{.qualified_name = "test.main.bitmap",
.short_name = "bitmap",
.label = "Class",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.bitmap.Set",
.short_name = "Set",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.bitmap"},
{.qualified_name = "test.main.FeatureSet",
.short_name = "FeatureSet",
.label = "Type",
.def_module_qn = "test.main",
.alias_target = "bitmap"},
{.qualified_name = "test.main.Defined",
.short_name = "Defined",
.label = "Class",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.Framework",
.short_name = "Framework",
.label = "Struct",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.Framework.NewFeatureSet",
.short_name = "NewFeatureSet",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.Framework",
.return_types = "FeatureSet"},
{.qualified_name = "test.main.Framework.NewDefined",
.short_name = "NewDefined",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.Framework",
.return_types = "Defined"},
{.qualified_name = "test.main.other",
.short_name = "other",
.label = "Struct",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.other.Set",
.short_name = "Set",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.other"},
};

static const char *go_crossfile_alias_source = "package main\n\n"
"func useAlias(f *Framework) {\n"
"\treqs := f.NewFeatureSet()\n"
"\treqs.Set(3)\n}\n\n"
"func useDefined(f *Framework) {\n"
"\td := f.NewDefined()\n"
"\td.Set(3)\n}\n";

static int go_crossfile_alias_check(const CBMResolvedCallArray *out) {
int idx = find_resolved_arr_confident(out, "useAlias", ".Set");
if (idx < 0 || strcmp(out->items[idx].callee_qn, "test.main.bitmap.Set") != 0) {
printf(" resolved calls (%d):\n", out->count);
for (int i = 0; i < out->count; i++) {
const CBMResolvedCall *rc = &out->items[i];
printf(" %s -> %s [%s %.2f]\n", rc->caller_qn ? rc->caller_qn : "(null)",
rc->callee_qn ? rc->callee_qn : "(null)", rc->strategy ? rc->strategy : "(null)",
rc->confidence);
}
}
ASSERT_GTE(idx, 0);
ASSERT_STR_EQ(out->items[idx].callee_qn, "test.main.bitmap.Set");
/* Reached through alias_of, so the emitter reports it the way it reports
* a same-file alias today (lsp_embed_dispatch): typed, not a name guess. */
ASSERT_TRUE(strncmp(out->items[idx].strategy, "lsp_", 4) == 0);
ASSERT_TRUE(strcmp(out->items[idx].strategy, "lsp_unresolved") != 0);
ASSERT_TRUE(out->items[idx].confidence >= 0.9f);
/* A defined type has its own (empty) method set. */
ASSERT_EQ(find_resolved_arr_confident(out, "useDefined", "bitmap.Set"), -1);
return 0;
}

TEST(golsp_crossfile_type_alias_method) {
int n = (int)(sizeof(go_crossfile_alias_defs) / sizeof(go_crossfile_alias_defs[0]));
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};

cbm_run_go_lsp_cross(&arena, go_crossfile_alias_source, (int)strlen(go_crossfile_alias_source),
"test.main", go_crossfile_alias_defs, n, NULL, NULL, 0, NULL, &out);
if (go_crossfile_alias_check(&out) != 0) {
cbm_arena_destroy(&arena);
return 1;
}

cbm_arena_destroy(&arena);
PASS();
}

TEST(golsp_crossfile_type_alias_method_prebuilt) {
int n = (int)(sizeof(go_crossfile_alias_defs) / sizeof(go_crossfile_alias_defs[0]));
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};

CBMTypeRegistry *reg = cbm_go_build_cross_registry(&arena, go_crossfile_alias_defs, n);
ASSERT_NOT_NULL(reg);
cbm_run_go_lsp_cross_with_registry(&arena, go_crossfile_alias_source,
(int)strlen(go_crossfile_alias_source), "test.main", reg,
NULL, NULL, 0, NULL, &out);
if (go_crossfile_alias_check(&out) != 0) {
cbm_arena_destroy(&arena);
return 1;
}

cbm_arena_destroy(&arena);
PASS();
}

TEST(golsp_crossfile_local_interface_single_impl) {
const char *source =
"package main\n\n"
Expand Down Expand Up @@ -1542,6 +1663,8 @@ SUITE(go_lsp) {
RUN_TEST(golsp_crossfile_interface_dispatch);
RUN_TEST(golsp_crossfile_interface_field_chain);
RUN_TEST(golsp_crossfile_aliased_field_requal);
RUN_TEST(golsp_crossfile_type_alias_method);
RUN_TEST(golsp_crossfile_type_alias_method_prebuilt);
RUN_TEST(golsp_crossfile_map_index);
RUN_TEST(golsp_crossfile_stdlib_interface);
RUN_TEST(golsp_crossfile_local_interface_single_impl);
Expand Down
Loading
Loading