Repository navigation
Go member calls resolve to nothing across files, and cannot cross a repo boundary at all #3393
Description
Net-new over v8 (verified @6478eb71, 0.9.80)
v8 today has no Go receiver typing at all: no _resolve_go_member_calls and no
go_member_calls registration in graphify/extract.py; extractors/go.py carries no
receiver name/type through function_bodies and stamps no _callable/_callable_class
on Go nodes; and go is absent from _LANG_SUFFIXES in cross_repo_calls.py (which
lists only cpp/csharp/java/swift). The recent member-call work on v8 is Python (#4198,
#2860), C# (#3797) and PowerShell (#4163) — none of it Go.
Still missing, and what the paired PR adds: resolve recv.M() through the receiver's
declared type to a method in another file, the go.py plumbing + _callable_class
markers that requires, and parking the call so a merged graph can finish it (#3152).
Fully net-new; nothing here is pre-empted.
What happens
A Go method call on a receiver resolves only when the bare method name happens to
match a symbol declared in the caller's own file. Anything else produces no edge:
// svc/greeter.go
package svc
type Greeter struct{}
func (g *Greeter) Greet() {}// svc/app.go
package svc
type App struct {
greeter *Greeter
}
func (a *App) Run() { a.greeter.Greet() } // no `calls` edgeextract_go reads the receiver's identifier and discards it unless it names an
imported package, so the shape Go dependency injection actually takes — a struct
field used from a method — contributes nothing to the call graph. The shared
cross-file pass then skips member callees outright (obj.log() → "log" has no
import evidence), and no _resolve_go_* resolver is registered to pick them up.
No test in the repo asserts that a cross-file Go receiver method call yields an
edge; test_go_builtin_call_targets.py states the omission explicitly.
Merging two repos loses the same edges for the same reason, plus one more: Go
nodes never carry the _callable / _callable_class markers (#2438), because
extract_go is the one extractor outside the tree-sitter engine and the engine is
the only place that stamps them. link_cross_repo_member_calls requires
_callable_class on the declaring type, so a Go type could not answer a parked
call even if one existed.
What it should do
Bind the call through the receiver's declared type, as the Swift (#1356), C++
(#1547), ObjC (#1556) and C# (#1609) resolvers already do for their languages —
and when the type is declared nowhere in the build, park the call so a merged
graph can finish it (#3152).
Go writes the receiver's type down in four places, all of them cheap to read:
| shape | source |
|---|---|
type App struct { greeter *Greeter } |
struct field |
func (s *Server) M() / func f(g *Greeter) |
parameter |
var g Greeter |
var declaration |
g := Greeter{} / g := &Greeter{} |
composite literal |
The single-definition guard has to stay: a Go type node id folds in the package
directory, so two packages declaring Greeter are two nodes and without import
evidence neither one is the answer.
Why it matters
Go is a backend language and services are split across repositories; the call
graph across that boundary is the reason to merge two graphs at all. Together with
the ObjC (#3384), TS/JS (#3386), Kotlin (#3388) and PHP (#3390) gaps, this covers
the languages a mobile-plus-backend org actually ships.
PR to follow.
Activity
xiongjianxu commentedon Sep 22, 2026
Here is a self-contained, runnable reproduction (no module/toolchain setup — just
graphifyonPYTHONPATH). It covers both halves: the cross-file gap in a single repo and the cross-repo gap aftermerge-graphs.On pristine v8 (
20a20d3):The
a.greeter.Greet()call (receiver is a struct field of type*Greeter) produces nocallsedge even whenGreeteris declared in a sibling file, and nothing is parked, somerge-graphscannot recover it across repos either — exactly the two shapes described above.With PR #3395 (
feat/go-cross-repo-member-calls):Cross-file is resolved directly; cross-repo is parked by name (no node ids, per the #3152 contract) and finished as an
INFERRED/context=cross_repoedge bylink_cross_repo_member_calls.PR #3395 cherry-picks clean onto the current v8 tip
20a20d3(git merge-treereports no conflicts; PR isMERGEABLE/CLEAN), so it can be picked up as-is or rebased on request.safishamsi commentedon Oct 13, 2026
Fixed in v0.9.89 (
graphifyy 0.9.89on PyPI) via #3395.