Repository navigation
Go: weak short-name strategies fabricate CALLS edges for selector calls (stdlib receivers hijack project symbols) #1906
Description
Status: fix open — PR #1907 (extractor is_method flag for Go selector calls + Go-gated cbm_go_suppress_weak_method_match). Full post-fix census inline below.
Version
codebase-memory-mcp 0.10.8 (also reproduced on a source build @ d706c33)
Platform
macOS (Apple Silicon)
Install channel
Built from source
Binary variant
standard
What happened, and what did you expect?
A Go selector call whose receiver the Go LSP cannot type falls through to the generic registry, which binds it by bare short name to an arbitrary same-named project symbol (suffix_match / unique_name). Stdlib/third-party calls are the worst case: the real callee is not in the tree, so every candidate is wrong by construction. Go never sets is_method, so the receiver-aware guard TS/JS got in #592/#606 (and Perl in #476) never fires.
Measured (private Go monorepo, ~19.6k CALLS): suffix_match 3817 @ avg 0.30, unique_name 3162 @ avg 0.53 — 36% of all CALLS from name guesses. One 14-line checksum helper touching only os/io/crypto/sha256 got 3/3 false outbound edges: f.Close() → a project Close (conf 0.11, 15 candidates), sha256.New() → an unrelated New, io.Copy → the sole project Copy (unique_name 0.375). trace_path surfaces all of it unfiltered, so Close/New/Run accumulate dozens of phantom callers.
Expected: no CALLS edge for a call the resolver cannot place; typed, local, and import-qualified calls keep resolving.
Reproduction
Dummy fixture: go.mod (module example.com/myapp) + storage/storage.go (the lone project Close method + a typed same-package Boot caller) + hash/hash.go:
package hash
import (
"os"
"example.com/myapp/util"
)
func FileLen(path string) int64 {
f, err := os.Open(path)
if err != nil {
return 0
}
defer f.Close() // ← false CALLS edge: hash.FileLen -> storage.Close
st, err := f.Stat()
if err != nil {
return 0
}
return st.Size()
}
func UsesUtil() string { return util.Tag() } // import-qualified: must keep resolvingActual: FileLen | Close | {"strategy":"unique_name","confidence":0.75,…} (with ≥2 candidates: suffix_match @ 0.1–0.4). Expected: no FileLen → Close; Boot → Close, bare local calls, and UsesUtil → Tag all survive.
Two Go-specific constraints (why the TS/JS drop-list can't be reused as-is)
field_type_hintis kept for Go — but see fix(pipeline): field_type_hint binds by receiver variable-name substring, not declared type — 1484 edges at conf 0.85 on one Go repo #1927: its receiver-awareness premise did not hold and is fixed separately (fix(pipeline): bind field_type_hint by owning QN segment, not substring #1936).unique_namedrops only when penalized (CONF_UNIQUE_NAMEscaled 0.75 → 0.375 when the lone candidate is outside the caller's import closure — the stdlib-hijack shape). Unpenalized lone candidates are genuinely typed and must keep resolving.
Post-fix strategy census (same tree indexed with v0.10.8 vs Stack A = #1907+#1913+#1915)
| strategy | v0.10.8 | with the fixes | Δ |
|---|---|---|---|
lsp_strategy_cross_file |
5273 | 6468 | +23% |
lsp_direct |
3033 | 3372 | +11% |
lsp_type_dispatch |
137 | 687 | +401% |
lsp_interface_dispatch |
17 | 79 | +365% |
lsp_embed_dispatch |
— | 42 | new |
import_map |
284 | 88 | −69% |
field_type_hint |
493 | 1484 | +201% — see #1927/#1936 |
unique_name |
3162 (avg 0.53) | 1573 (avg 0.68) | −50% |
suffix_match |
3817 (avg 0.30) | 113 | −97% |
same_module |
3267 | 224 | −93% |
| CALLS total | 19653 | 14316 | −27% |
Weak short-name share (suffix_match+unique_name+callee_suffix): 36.0% → 12.4%, with lsp_* rising 8481 → 10672 in absolute terms — the edges were re-resolved, not merely deleted. The field_type_hint growth is the direct consequence tracked as #1927 (guesses moved from a visibly-weak 0.30 bucket into a 0.85 one); fixed by #1936.
Related: #592/#606 (TS/JS), #476 (Perl), #1893 (Swift), #1276 (Python), #1114; tracked in #1932.
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): bind field_type_hint by owning QN segment, not substring
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): bind field_type_hint by owning QN segment, not substring
feat(extract): make cgo visible - //export contracts, C. veto, preamb…
fix: stop discarding the graph engine's per-edge confidence, and thre…
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): a bare Go reference never binds a struct Field
fix(pipeline): bind field_type_hint by owning QN segment, not substring
feat(extract): make cgo visible - //export contracts, C. veto, preamb…
fix(pipeline): bind field_type_hint by owning QN segment, not substring
test(pipeline): bind the Go selector guard on the parallel resolver
DeusData commentedon Oct 20, 2026
Fixed on main by 5a4af9c (#1907, merged together with #1913). Thank you, @ilyabrykau-orca, for the precise report and the fix. On prometheus, SCIP-Go precision went from 0.647 to 0.970.