diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 37bdc26d4..56a85e8a2 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -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 diff --git a/src/pipeline/registry.c b/src/pipeline/registry.c index bfc2e330e..9b5165a27 100644 --- a/src/pipeline/registry.c +++ b/src/pipeline/registry.c @@ -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 diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index d413aa9bb..6d04ad327 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -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); diff --git a/tests/test_registry.c b/tests/test_registry.c index a6fbd6201..90ab69170 100644 --- a/tests/test_registry.c +++ b/tests/test_registry.c @@ -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));