fix(go): a bare init never binds to a package init or an init method

Go gives `init` no binding in the package block: init functions cannot
be referred to from anywhere in a program, and a method or field named
init needs a selector. A bare `init` is therefore always a local name -
a closure (`init := func(ctx) {...}; init(ctx)`), a parameter, or the
`package init` clause of a directory named init.

Before #1915 same_module bound such a name to the flat package init.
With per-file init QNs it now falls through to the short-name fallback
and binds unrelated methods named init. On kubernetes that minted 24
CALLS (22 e2e storage DefineTests closures among them), 28 WRITES,
7 USAGE and one derived TESTS edge, all false.

cbm_go_suppress_bare_init_bind refuses, for a bare Go `init`, any
registry binding onto a Function (every Go Function named init is a
package init) and a weak short-name binding onto a Method or Field.
lsp_* answers and selector calls (`w.init()`) are untouched. Wired at
the CALLS, USAGE and READS/WRITES seams of both resolvers.

Kubernetes, full index, main vs branch: only those 60 edges removed,
nodes identical, no resolver edge added; the 14 LSP-typed selector
CALLS into init methods are unchanged.

Tests: pipeline_go_bare_init_binds_nothing (+ parallel twin) are RED
with the predicate disabled (closure CALLS, parameter CALLS, closure
WRITES, package-clause USAGE onto Watcher.init) and GREEN with it; the
selector control keeps Watcher.Start/Boot -> Watcher.init.
go_bare_init_never_binds_init_namesake pins the predicate.

Refs #1915

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-10-31 14:16:05 +01:00
1 parent 9faa3e5c06
commit 4a78fe6a6b
7 files changed
+327

No files matched your search

+8
View File
@@ -1344,6 +1344,14 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
if (cbm_java_suppress_call_to_data_member(lang == CBM_LANG_JAVA, target_node->label)) { if (cbm_java_suppress_call_to_data_member(lang == CBM_LANG_JAVA, target_node->label)) {
return 0; return 0;
} }
/* A bare Go `init(...)` calls a local closure or parameter: never a
* package init (unreferable in Go) and never a same-named method found
* by name alone — see cbm_go_suppress_bare_init_bind. MUST match
* pass_parallel.c exactly. */
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, call->is_method, call->callee_name,
res.strategy, target_node->label)) {
return 0;
}
evidence->candidate_qn = target_node->qualified_name; evidence->candidate_qn = target_node->qualified_name;
evidence->calls_emitted |= evidence->calls_emitted |=
emit_classified_edge(ctx, call, source_node, target_node, &res, module_qn, imp_keys, emit_classified_edge(ctx, call, source_node, target_node, &res, module_qn, imp_keys,
+19
View File
@@ -3629,6 +3629,13 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
* Variable or a Field. */ * Variable or a Field. */
continue; continue;
} }
if (target_node && source_node->id != target_node->id &&
cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, call->is_method, call->callee_name,
res.strategy, target_node->label)) {
/* Same guard as pass_calls.c — a bare Go `init` is a local name,
* never a package init or a method found by name alone. */
continue;
}
if (!target_node || source_node->id == target_node->id) { if (!target_node || source_node->id == target_node->id) {
/* HTTP/ASYNC calls to an EXTERNAL client library (`requests.get(url)`) /* HTTP/ASYNC calls to an EXTERNAL client library (`requests.get(url)`)
* resolve to an unindexed QN (target_node == NULL), but their edge * resolve to an unindexed QN (target_node == NULL), but their edge
@@ -3805,6 +3812,12 @@ static void resolve_file_usages(resolve_ctx_t *rc, resolve_worker_state_t *ws,
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) { if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
continue; continue;
} }
/* A bare Go `init` names a local (cbm_go_suppress_bare_init_bind).
* Mirrors the sequential twin (pass_usages.c). */
if (tgt && cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, usage->is_member_access,
usage->ref_name, res.strategy, tgt->label)) {
continue;
}
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow || if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
cbm_pipeline_node_is_callable_target(tgt))) { cbm_pipeline_node_is_callable_target(tgt))) {
continue; continue;
@@ -3933,6 +3946,12 @@ static void resolve_file_rw(resolve_ctx_t *rc, resolve_worker_state_t *ws, CBMFi
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) { if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
continue; continue;
} }
/* A bare Go `init` names a local (cbm_go_suppress_bare_init_bind).
* Mirrors the sequential twin (pass_usages.c). */
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, rw->is_member_access, rw->var_name,
res.strategy, tgt->label)) {
continue;
}
const char *etype = rw->is_write ? "WRITES" : "READS"; const char *etype = rw->is_write ? "WRITES" : "READS";
cbm_gbuf_insert_edge(ws->local_edge_buf, src->id, tgt->id, etype, "{}"); cbm_gbuf_insert_edge(ws->local_edge_buf, src->id, tgt->id, etype, "{}");
} }
+13
View File
@@ -288,6 +288,12 @@ static int resolve_usage_edges(cbm_pipeline_ctx_t *ctx, const CBMFileResult *res
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) { if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
continue; continue;
} }
/* A bare Go `init` names a local, never a package init or an init
* method (cbm_go_suppress_bare_init_bind). Mirrors pass_parallel.c. */
if (tgt && cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, usage->is_member_access,
usage->ref_name, res.strategy, tgt->label)) {
continue;
}
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow || if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
cbm_pipeline_node_is_callable_target(tgt))) { cbm_pipeline_node_is_callable_target(tgt))) {
continue; continue;
@@ -420,6 +426,13 @@ static int resolve_rw_edges(cbm_pipeline_ctx_t *ctx, const CBMFileResult *result
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) { if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
continue; continue;
} }
/* `init := func(...)`: a bare Go `init` names a local, never a package
* init or an init method (cbm_go_suppress_bare_init_bind). Mirrors
* pass_parallel.c. */
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, rw->is_member_access, rw->var_name,
res.strategy, tgt->label)) {
continue;
}
const char *edge_type = rw->is_write ? "WRITES" : "READS"; const char *edge_type = rw->is_write ? "WRITES" : "READS";
cbm_gbuf_insert_edge(ctx->gbuf, src->id, tgt->id, edge_type, "{}"); cbm_gbuf_insert_edge(ctx->gbuf, src->id, tgt->id, edge_type, "{}");
+10
View File
@@ -459,6 +459,16 @@ bool cbm_suppress_external_import_shadow(const char *callee_name, const char *st
bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *strategy, bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *strategy,
double confidence); double confidence);
/* A bare Go `init` (a call or reference that is not the member half of a
* selector) is always a local name: Go forbids referring to a package init
* function, and a method or field needs a selector. True when such a name
* resolved onto a Function (every Go Function named init is a package init;
* any registry strategy) or onto a Method or Field found by a weak short-name
* strategy. lsp_* answers are kept. Go only. Pure; unit-tested in
* test_registry.c; wired in pass_calls.c, pass_usages.c and pass_parallel.c. */
bool cbm_go_suppress_bare_init_bind(bool is_go, bool is_member, const char *name,
const char *strategy, const char *target_label);
/* Import-binding counterpart (#2127). True when the callee's root identifier /* Import-binding counterpart (#2127). True when the callee's root identifier
* is bound by an import of this file whose module chain contradicts the * is bound by an import of this file whose module chain contradicts the
* resolved target (`from unittest.mock import patch; patch()` must not bind a * resolved target (`from unittest.mock import patch; patch()` must not bind a
+40
View File
@@ -1015,6 +1015,46 @@ bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *s
return confidence < CONF_UNIQUE_NAME; return confidence < CONF_UNIQUE_NAME;
} }
bool cbm_go_suppress_bare_init_bind(bool is_go, bool is_member, const char *name,
const char *strategy, const char *target_label) {
/* The Go spec gives `init` no binding in the package block: the
* identifier declares init functions and nothing else, and "init
* functions cannot be referred to from anywhere in a program". A method
* or a field named init is reached only through a selector (`x.init()`,
* `e.init`), which arrives here as is_member and is left alone. So a
* BARE `init` is always a local name: a closure (`init :=
* func(ctx) {...}; init(ctx)`, 22 kubernetes e2e storage suites), a
* parameter (`func sum(init func() ref.Val)`), or the `package init`
* clause of a directory named init. None of them is a project node.
*
* Every Go Function named init is a package init (no other package-level
* `init` can be declared), so that target is refused for every registry
* strategy. Before #1915 the flat QN made same_module bind the bare name
* to its own package's init; after it, the per-file QN misses there and
* the name fallback binds unrelated init methods instead. A Method or
* Field namesake is refused when only the name found it (the shared weak
* drop-list), which is how every such edge was minted. lsp_* answers are
* untouched: the sequential resolver emits them before its registry
* guards run, so refusing one here would split the two resolvers.
*
* Go-gated: elsewhere `init` is an ordinary name. A bare `init()` in a
* Java, C#, C++, Kotlin or Ruby method calls the class's own method, and
* a C, Python or JS `init` function is called by its bare name. */
if (!is_go || is_member || !name || strcmp(name, "init") != 0 || !target_label) {
return false;
}
if (strategy && strncmp(strategy, "lsp_", strlen("lsp_")) == 0) {
return false;
}
if (strcmp(target_label, "Function") == 0) {
return true;
}
if (strcmp(target_label, "Method") == 0 || strcmp(target_label, "Field") == 0) {
return weak_short_name_strategy(strategy);
}
return false;
}
/* Import-binding counterpart of the parameter guard above (#2127). An import /* Import-binding counterpart of the parameter guard above (#2127). An import
* binds the identifier for the whole module; when that import is external it * binds the identifier for the whole module; when that import is external it
* never materializes, and the chain falls through to project-wide short-name * never materializes, and the chain falls through to project-wide short-name
+197
View File
@@ -11071,6 +11071,201 @@ TEST(pipeline_go_init_lsp_edges_per_file_parallel) {
PASS(); PASS();
} }
/* Fixture for the bare-`init` probes (sequential and parallel twins), the
* shapes the kubernetes census found after #1915: a local closure `init :=
* func(ctx)` that is called, written and passed by value (the e2e storage
* suites), a parameter named init (`func sum(init func() ref.Val)`), and a
* `package init` directory. The decoys are a package init in the caller's
* own package (what same_module bound before #1915) and an unrelated
* Watcher.init method (what the name fallback binds after it). Watcher.Start
* and Boot call that method through a selector, the control that must keep
* resolving. pad_files > 0 adds filler Go files past MIN_FILES_FOR_PARALLEL. */
static void write_go_bare_init_fixture(const char *tmp, int pad_files) {
write_temp_file(tmp, "go.mod", "module example.com/fxbareinit\n\ngo 1.22\n");
write_temp_file(tmp, "suites/suite.go",
"package suites\n"
"\n"
"import \"context\"\n"
"\n"
"var registered []string\n"
"\n"
"func init() { registered = append(registered, \"volumes\") }\n"
"\n"
"func wrap(fn func(context.Context)) { _ = fn }\n"
"\n"
"func DefineTests(ctx context.Context) {\n"
"\tinit := func(ctx context.Context) {\n"
"\t\t_ = ctx\n"
"\t}\n"
"\tinit(ctx)\n"
"\twrap(init)\n"
"}\n"
"\n"
"func Sum(init func() int) int {\n"
"\treturn init()\n"
"}\n");
write_temp_file(tmp, "watcher/watcher.go",
"package watcher\n"
"\n"
"type Watcher struct{ ready bool }\n"
"\n"
"func NewWatcher() *Watcher { return &Watcher{} }\n"
"\n"
"func (w *Watcher) init() { w.ready = true }\n"
"\n"
"func (w *Watcher) Start() {\n"
"\tw.init()\n"
"}\n"
"\n"
"func Boot() {\n"
"\tw := NewWatcher()\n"
"\tw.init()\n"
"}\n");
write_temp_file(tmp, "boot/init/init.go",
"// Package init installs defaults for its importers.\n"
"package init\n"
"\n"
"func init() { setup() }\n"
"\n"
"func setup() {}\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);
}
}
static bool bare_init_qn_ends_with(const char *qn, const char *suffix) {
size_t ql = strlen(qn);
size_t sl = strlen(suffix);
return ql >= sl && strcmp(qn + ql - sl, suffix) == 0;
}
/* Resolver edges into any node named `init` whose source is not one of the
* two selector controls. Every such edge in the fixture binds a bare `init`,
* which in Go is always a local name. */
static int go_bare_init_false_edges(cbm_store_t *s, const char *project) {
static const char *const types[] = {"CALLS", "USAGE", "WRITES", "READS", "CALL_REFERENCE"};
int hits = 0;
for (size_t t = 0; t < sizeof(types) / sizeof(types[0]); t++) {
cbm_edge_t *edges = NULL;
int edge_count = 0;
cbm_store_find_edges_by_type(s, project, types[t], &edges, &edge_count);
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.name && strcmp(tgt.name, "init") == 0 &&
!bare_init_qn_ends_with(src.qualified_name, ".watcher.Watcher.Start") &&
!bare_init_qn_ends_with(src.qualified_name, ".watcher.Boot")) {
printf(" bare init bound: %s %s -> %s\n", types[t], src.qualified_name,
tgt.qualified_name ? tgt.qualified_name : "?");
hits++;
}
cbm_node_free_fields(&src);
cbm_node_free_fields(&tgt);
}
if (edges) {
cbm_store_free_edges(edges, edge_count);
}
}
return hits;
}
static int assert_go_bare_init_edges(cbm_store_t *s, const char *project) {
/* The decoys exist, so a zero below is a refused binding and not an
* absent target: two package inits and the Watcher method. */
cbm_node_t *inits = NULL;
int ic = 0;
cbm_store_find_nodes_by_name(s, project, "init", &inits, &ic);
int functions = 0;
int methods = 0;
for (int i = 0; i < ic; i++) {
functions += inits[i].label && strcmp(inits[i].label, "Function") == 0;
methods += inits[i].label && strcmp(inits[i].label, "Method") == 0;
}
cbm_store_free_nodes(inits, ic);
ASSERT_EQ(functions, 2);
ASSERT_EQ(methods, 1);
/* NEGATIVE: no bare `init` (closure call, closure write, closure passed
* by value, parameter call, `package init`) binds a package init or the
* unrelated method. */
ASSERT_EQ(go_bare_init_false_edges(s, project), 0);
/* CONTROL: a selector call `w.init()` is not bare and still resolves to
* the method, from a method body and from a free function. */
ASSERT_GTE(
go_calls_between(s, project, ".watcher.Watcher.Start", ".watcher.Watcher.init", NULL), 1);
ASSERT_GTE(go_calls_between(s, project, ".watcher.Boot", ".watcher.Watcher.init", NULL), 1);
return 0;
}
/* R45 (follow-up to #1915): a bare Go `init` never binds a package init or,
* by name alone, an init method (cbm_go_suppress_bare_init_bind). */
TEST(pipeline_go_bare_init_binds_nothing) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_bare_init_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_go_bare_init_fixture(tmp, 0);
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/go_bare_init.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_bare_init_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, which carries its own copy of each guard. */
TEST(pipeline_go_bare_init_binds_nothing_parallel) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_bare_init_par_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_go_bare_init_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_bare_init_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_bare_init_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 /* 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 twins). pad_files > 0 adds filler files to push the index over the
* parallel-pipeline threshold, since USAGE/WRITES/READS have one resolver per * parallel-pipeline threshold, since USAGE/WRITES/READS have one resolver per
@@ -29621,6 +29816,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_go_multi_init_nodes_survive); 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);
RUN_TEST(pipeline_go_init_lsp_edges_per_file_parallel); RUN_TEST(pipeline_go_init_lsp_edges_per_file_parallel);
RUN_TEST(pipeline_go_bare_init_binds_nothing);
RUN_TEST(pipeline_go_bare_init_binds_nothing_parallel);
RUN_TEST(pipeline_go_crossfile_type_alias_method); RUN_TEST(pipeline_go_crossfile_type_alias_method);
RUN_TEST(pipeline_go_crossfile_type_alias_method_parallel); RUN_TEST(pipeline_go_crossfile_type_alias_method_parallel);
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c); RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
+40
View File
@@ -1260,6 +1260,45 @@ TEST(go_bare_ref_never_binds_field) {
PASS(); PASS();
} }
TEST(go_bare_init_never_binds_init_namesake) {
/* Go cannot refer to a package init function, and a method or field
* named init needs a selector, so a bare `init` (a closure, a parameter,
* a `package init` clause) is always a local name. */
/* Every Go Function named init is a package init: refused for every
* registry strategy, same_module included (the pre-#1915 binding). */
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "same_module", "Function"));
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Function"));
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "import_map", "Function"));
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", NULL, "Function"));
/* A Method or Field namesake found by the name alone is refused. */
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Method"));
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "unique_name", "Method"));
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Field"));
/* ... but not one that an import- or module-aware strategy produced. */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "same_module", "Method"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "import_map", "Method"));
/* lsp_* answers are kept: the sequential resolver emits them before its
* registry guards run, so refusing one here would split the resolvers. */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "lsp_direct", "Function"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "lsp_direct", "Method"));
/* The member half of a selector (`w.init()`, `e.init`) is not bare. */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, true, "init", "suffix_match", "Method"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, true, "init", "suffix_match", "Field"));
/* Other names and other target kinds are untouched. */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "initAll", "suffix_match", "Method"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "Init", "suffix_match", "Function"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Variable"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Module"));
/* Go only: elsewhere a bare `init()` is an ordinary call (a Java or C++
* method body calls its own init, a C or Python init is a function). */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(false, false, "init", "suffix_match", "Method"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(false, false, "init", "same_module", "Function"));
/* Degenerate input → nothing to judge. */
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, NULL, "suffix_match", "Function"));
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", NULL));
PASS();
}
TEST(c_bare_ref_never_binds_field) { TEST(c_bare_ref_never_binds_field) {
/* #2666: a C struct member is named only as the member half of `a.b`, /* #2666: a C struct member is named only as the member half of `a.b`,
* `a->b` or a designator `.b` (field_identifier → is_member_access). A * `a->b` or a designator `.b` (field_identifier → is_member_access). A
@@ -2656,6 +2695,7 @@ SUITE(registry) {
RUN_TEST(registry_tie_prefers_csharp_implementation_over_ref_stub); RUN_TEST(registry_tie_prefers_csharp_implementation_over_ref_stub);
RUN_TEST(cross_language_ref_drops_go_vs_c); RUN_TEST(cross_language_ref_drops_go_vs_c);
RUN_TEST(go_bare_ref_never_binds_field); RUN_TEST(go_bare_ref_never_binds_field);
RUN_TEST(go_bare_init_never_binds_init_namesake);
RUN_TEST(c_bare_ref_never_binds_field); RUN_TEST(c_bare_ref_never_binds_field);
RUN_TEST(call_onto_field_follows_language_and_shape); RUN_TEST(call_onto_field_follows_language_and_shape);
RUN_TEST(c_bare_call_does_not_weigh_field_namesakes); RUN_TEST(c_bare_call_does_not_weigh_field_namesakes);