mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(go): no field-type hint for Go; it is a name guess, par-only
The parallel resolver re-picks a multi-candidate registry match through try_field_type_hint. That helper capitalizes the receiver VARIABLE name and accepts any candidate whose QN contains it as a substring: `t.Run` on a *testing.T became `T` and bound a project `TBench.Run`. The sequential resolver has no such step. The Go selector guard treated field_type_hint as receiver-aware and kept it, so the same weak pick it drops in sequential mode survived in parallel mode under a new name. On caddy + prometheus, parallel and sequential CALLS diverged by 1,684 (this branch) and 5,560 (with receiver-qualified method QNs, which put type names into more QNs). At base the gap is 84. SCIP precision of Go field_type_hint is 0.016 at base. Go no longer takes the hint. The guard keeps field_type_hint on the shared weak list, and the fixture of both twins now pins the `t.Run` case. A typed field call (lrp_go_s8, `s.repo.Find()`) resolves through the Go LSP and is unaffected. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
7528ec06fe
commit
e554a2ab58
4 files changed
+51
-16
No files matched your search
@@ -3298,7 +3298,15 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
}
|
||||
|
||||
_rc_t0 = extract_now_ns();
|
||||
try_field_type_hint(rc, &res, call->callee_name, source_node->id);
|
||||
/* Not for Go (F-G9): the hint matches the capitalized receiver
|
||||
* VARIABLE name as a substring of a candidate QN (`t.Run` -> any
|
||||
* `...T...Run`), with no type evidence. The sequential resolver has no
|
||||
* such step, so for Go it produced parallel-only edges (par/seq CALLS
|
||||
* diverged), and the Go selector guard could not see them as the weak
|
||||
* pick they are. SCIP precision of Go field_type_hint: 0.016. */
|
||||
if (lang != CBM_LANG_GO) {
|
||||
try_field_type_hint(rc, &res, call->callee_name, source_node->id);
|
||||
}
|
||||
/* Cross-language veto — the same predicate as pass_calls.c, applied
|
||||
* AFTER the field-type hint so the hint may still re-pick a compatible
|
||||
* candidate first, and BEFORE the empty-resolution fallbacks below so a
|
||||
|
||||
@@ -755,18 +755,16 @@ bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *s
|
||||
* short-name strategy binds it to an arbitrary same-named project symbol
|
||||
* (`f.Close()` on an os.File -> a project `Close`, suffix_match over 15
|
||||
* candidates). Built on the shared drop-list so "weak" means one thing
|
||||
* for every guard; Go narrows it in two places only. */
|
||||
* for every guard; Go narrows it in one place only (unique_name below).
|
||||
* field_type_hint stays weak: it is a receiver-NAME substring match, not
|
||||
* a declared-type lookup (the parallel resolver no longer produces it for
|
||||
* Go; a typed field call such as lrp_go_s8's `s.repo.Find()` resolves
|
||||
* through the Go LSP). */
|
||||
if (!is_go || !is_method || !weak_short_name_strategy(strategy)) {
|
||||
return false;
|
||||
}
|
||||
/* field_type_hint is KEPT for Go: a Go struct field carries a declared
|
||||
* type, so the parallel resolver's field-type hint is receiver-aware for
|
||||
* Go (lrp_go_s8_field_type_hint), not a heuristic. */
|
||||
if (strcmp(strategy, "field_type_hint") == 0) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(strategy, "unique_name") != 0) {
|
||||
return true; /* suffix_match, fuzzy */
|
||||
return true; /* suffix_match, field_type_hint, fuzzy */
|
||||
}
|
||||
/* unique_name is dropped only when PENALIZED: resolve_name_lookup scales
|
||||
* CONF_UNIQUE_NAME by DEFAULT_CONFIDENCE exactly when the lone candidate
|
||||
|
||||
@@ -8713,6 +8713,30 @@ static void write_go_receiver_fixture(const char *tmp, int pad_files) {
|
||||
"func CallsLocal() int { return localHelper() }\n"
|
||||
"\n"
|
||||
"func UsesUtil() string { return util.Tag() }\n");
|
||||
/* F-G9: two project methods named Run, one under a directory whose QN
|
||||
* carries an uppercase T. `t.Run` on a *testing.T must bind neither. The
|
||||
* parallel resolver field-type hint matched the capitalized receiver
|
||||
* variable ("T") as a QN substring and produced a par-only edge. */
|
||||
write_temp_file(tmp, "TBench/bench.go",
|
||||
"package tbench\n"
|
||||
"\n"
|
||||
"type Bench struct{}\n"
|
||||
"\n"
|
||||
"func (b *Bench) Run(name string) bool { return name != \"\" }\n");
|
||||
write_temp_file(tmp, "worker/worker.go",
|
||||
"package worker\n"
|
||||
"\n"
|
||||
"type Worker struct{}\n"
|
||||
"\n"
|
||||
"func (w *Worker) Run(name string) bool { return false }\n");
|
||||
write_temp_file(tmp, "hash/hash_test.go",
|
||||
"package hash\n"
|
||||
"\n"
|
||||
"import \"testing\"\n"
|
||||
"\n"
|
||||
"func TestSubRun(t *testing.T) {\n"
|
||||
"\tt.Run(\"a\", func(t *testing.T) {})\n"
|
||||
"}\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
@@ -8750,6 +8774,8 @@ TEST(pipeline_go_receiver_suppresses_weak_method_edge) {
|
||||
/* (4) The import-qualified cross-package call survives (import-aware
|
||||
* strategies are outside the drop-list). */
|
||||
ASSERT_TRUE(cross_file_call_exists(s, project, "UsesUtil", "Tag"));
|
||||
/* (5) F-G9: the stdlib *testing.T Run binds no project Run. */
|
||||
ASSERT_FALSE(cross_file_call_exists(s, project, "TestSubRun", "Run"));
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
@@ -8793,6 +8819,8 @@ TEST(pipeline_go_receiver_parallel_suppresses_weak_method_edge) {
|
||||
ASSERT_TRUE(cross_file_call_exists(s, project, "Boot", "Close"));
|
||||
ASSERT_TRUE(cross_file_call_exists(s, project, "CallsLocal", "localHelper"));
|
||||
ASSERT_TRUE(cross_file_call_exists(s, project, "UsesUtil", "Tag"));
|
||||
/* F-G9: the parallel-only field-type hint must not bind `t.Run` either. */
|
||||
ASSERT_FALSE(cross_file_call_exists(s, project, "TestSubRun", "Run"));
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
|
||||
@@ -1673,11 +1673,12 @@ TEST(weak_call_guards_share_one_drop_list) {
|
||||
strategies[i], member, binding);
|
||||
}
|
||||
ASSERT_EQ(member, binding);
|
||||
/* The Go selector guard builds on the same list and narrows it in
|
||||
* exactly one place at a penalized confidence (0.75 * 0.5): it keeps
|
||||
* field_type_hint. Any other drift from the shared list fails here. */
|
||||
/* The Go selector guard builds on the same list. At a penalized
|
||||
* confidence (0.75 * 0.5) it agrees with it exactly; its one narrowing
|
||||
* (an unpenalized unique_name) does not apply here. Any drift from
|
||||
* the shared list fails here. */
|
||||
bool go = cbm_go_suppress_weak_method_match(true, true, strategies[i], 0.375);
|
||||
bool go_expected = member && strcmp(strategies[i], "field_type_hint") != 0;
|
||||
bool go_expected = member;
|
||||
if (go != go_expected) {
|
||||
printf(" Go drop-list divergence on strategy \"%s\": go=%d expected=%d\n",
|
||||
strategies[i], go, go_expected);
|
||||
@@ -1697,6 +1698,9 @@ TEST(go_suppress_drops_weak_selector_matches) {
|
||||
ASSERT_TRUE(cbm_go_suppress_weak_method_match(true, true, "suffix_match", 0.11));
|
||||
ASSERT_TRUE(cbm_go_suppress_weak_method_match(true, true, "fuzzy", 0.9));
|
||||
ASSERT_TRUE(cbm_go_suppress_weak_method_match(true, true, "unique_name", 0.375));
|
||||
/* field_type_hint is a receiver-NAME substring match, not type evidence
|
||||
* (F-G9): weak for Go like the shared list. */
|
||||
ASSERT_TRUE(cbm_go_suppress_weak_method_match(true, true, "field_type_hint", 0.85));
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -1706,9 +1710,6 @@ TEST(go_suppress_keeps_typed_and_import_aware_matches) {
|
||||
* lone-candidate call never enters the field-type-hint upgrade, so it must
|
||||
* survive (lrp_go_s8_field_type_hint). */
|
||||
ASSERT_FALSE(cbm_go_suppress_weak_method_match(true, true, "unique_name", 0.75));
|
||||
/* field_type_hint is receiver-aware for Go — struct fields carry declared
|
||||
* types (lrp_go_s8_field_type_hint) — so it stays, unlike the TS/JS list. */
|
||||
ASSERT_FALSE(cbm_go_suppress_weak_method_match(true, true, "field_type_hint", 0.85));
|
||||
ASSERT_FALSE(cbm_go_suppress_weak_method_match(true, true, "same_module", 0.9));
|
||||
ASSERT_FALSE(cbm_go_suppress_weak_method_match(true, true, "import_map", 0.95));
|
||||
ASSERT_FALSE(cbm_go_suppress_weak_method_match(true, true, "import_map_suffix", 0.9));
|
||||
|
||||
Reference in new issue
Block a user