Repository navigation
fix(go): a call through a type assertion reaches the asserted type's method - #2444
Merged
Merged
Conversation
…method Both extractors record `srv.(KVServer).Range(ctx, in)` as a bare `Range` call at the column where its receiver expression starts, as they record any call through an expression. Resolution matched the name alone, so every handler protoc-gen-go-grpc emits went to the `UnimplementedKVServer` stub beside the `KVServer` interface (70 edges in etcd), and calls like `c.Reader.(*pipe).Close()`, `p.(Pausable).Pause()` or `v.(featuregate.MutableFeatureGate).Set(…)` linked to a namesake or to nothing. The call's chain is now read back from its column (operand, selectors, assertions, calls and indexes, past strings, runes and comments, and on to the next line after a trailing `.`). When the link of the call's name follows an assertion, the call is a method of the asserted type, found where Go finds it: - a bare `T` or `*T` in the call's own package, or one it dot-imports; - a `pkg.T` in the imported project package; - its own method, the one its interface declares, or one promoted from a type it embeds, through the package-scoped lookup #2361 added. A type from outside the project (`http.Flusher`), a predeclared one (`error`) or a type literal (`interface{ Flush() }`) links nothing. The check runs ahead of the framework, import and name strategies, and ahead of the built-in filter, which dropped a method named `close` or `copy` called through an assertion. Two calls of one name in a chain share the column (`b.(*Builder).Add(1).Add(2)`); both are taken for the one made through the assertion, whose edge is there either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…svaraya-736b75 # Conflicts: # src/resolution/index.ts # src/resolution/name-matcher.ts
Since #2417 a Go `type A = B` is a node, and an alias of a type from outside the project (`type Ctx = context.Context`) makes the package-scoped method lookup step aside so the method is found by name, as before aliases had nodes. For a call through an assertion to such an alias, `v.(Ctx).Done()`, that fallback took any project method named `Done`. The asserted type's methods are the outside type's, so the call now links nothing. An alias of a project type (`type Keeper = storage.Store`) is followed to the type it names, which the tests now pin as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Go call through a type assertion, like
srv.(KVServer).Range(ctx, in), reaches the resolver by its bare member name (Range), recorded at the column where its receiver expression starts, the way every call through an expression is. Resolution matched the name alone:Unimplemented…Serverstub beside the server interface (etcd'sapi/etcdserverpb/rpc_grpc.pb.go:223→UnimplementedKVServer::Range, exact-match 0.7; 72 such edges in etcd, 3 in kratos, 95 in grpc-go);cfg.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(…)→ v2store'sStore::Set;stream.(grpc.ClientStream).SendMsg(…)→ an example'swrappedStream) or to nothing (p.(Pausable).Pause(),srv.(LockServer).Lock(…),c.Reader.(*pipe).Close()).Fix
matchGoAssertedCall(name-matcher) runs early inresolveOneInner, ahead of the framework, import and name strategies and of the built-in filter (which dropped a method namedcloseorcopy). It reads the call's chain back from its column: the operand, then each selector, assertion, call and index, past strings, runes and comments, and onto the next line after a trailing., where Go inserts no semicolon. When the link of the call's name follows an assertion, the call is a method of the asserted type, found where Go finds it:Tor*T(type arguments dropped) in the call's own package, or in a project package the file dot-imports;pkg.Tin the imported project package;resolveMethodOnTypewith that package directory, the package-scoped lookup fix(go): resolve unexported receivers and nested modules within the right package (#2323, #2322) #2361 added: the type's own method, the one its interface declares, or one promoted from a type it embeds; an alias is followed to the type it names (fix(go): a type aliastype A = Bis indexed and links to the type it names #2417).A type found in none of those (outside the project, like
http.Flusher, or predeclared, likeerror), an alias of an outside type (type Ctx = context.Context) and a type literal (interface{ Flush() }) link nothing. Two calls of one name in one chain (b.(*Builder).Add(1).Add(2)) share the column and can't be told apart; both are taken for the one made through the assertion, whose edge is real either way.Kernel: no change needed.
codegraph-kernel/src/go.rsextract_callfalls to its_arm for atype_assertion_expressionreceiver exactly as the TS extractor's finalelsedoes, so both emit the bare name at the call's start. The new test pins that on both backends (LF and CRLF), and the two paths' indexes agree on all 298 assertion sites in etcd and grpc-go (ref names, columns and statuses, edge targets).Validation
Regression test:
__tests__/go-type-assertion-calls.test.ts, 18 tests. The extraction check runs on the kernel and wasm; the indexed module runs on the default path and on wasm. Namesakes sit where name matching looked first: a stub beside each interface, a same-file type with each method name, and analphapackage that sorts ahead ofstorage. With main's resolver, 12 of the 14 resolution tests fail (6 per backend); the extraction checks and the guard that a later link of the chain is left alone pass there by design. Each guarded piece was mutation-checked: dropping dot-import lookup, string skipping, the hook's place ahead of the built-in filter, thenullfor a type the package doesn't declare, the alias check, or leaving two same-name links of a chain to name matching each turns a test red.Before/after edge diffs (site-keyed on (source, line, column, ref name), main ed199e6 vs this branch, same kernel; every changed site triaged against the source: the type asserted at the call, the package Go finds it in from go.mod and the file's imports, and each side's target):
Unimplemented…Serverstubs, 17 offAuthClient(a client interface sharing the RPC names), 10MutableFeatureGate.Setcalls off v2store'sStore::Setand cache'sready::Set. grpc-go: 95 off stubs, 28 off other namesakes (balancer.ConfigParser.ParseConfigoff each builder's own,grpc.ServerStream.RecvMsgoff an example'swrappedStream,*testutils.TestSubConnoff a wrapper in the caller's package, …). kratos: the 3GreeterServerhandler calls.p.(Pausable).Pause/Resume,srv.(LockServer).Lock/Unlock×4,iv.Val.(watcherSet).add(a defined map type's method),st1.(EtcdState).Equal,st1.(nonDeterministicState).Equal; grpc-goc.Reader.(*pipe).Close()(it used to collapse onto the callingconn.Close) andw.s.Handler.(*grpc.Server).Stop()(the root package'sServer).w.(http.Flusher).Flush()(was a test fake'sFlush), grpc-go(*v3orcapb.OrcaLoadReport).GetRequestCost()(cncf/xds; was interop'sTestOrcaReport), harbor 7 ×suite.userManager.(*mockUser.Manager).On(…): mockery mocks that embed testify'smock.Mock, soOnis testify's (the old edges went to harbor's privatemockableinterface by name).instance-method0.9 instead ofexact-match; 5 of harbor's resolve through the*cached.BaseManagera cached manager embeds, as before.type A = Bis indexed and links to the type it names #2417 and fix(go): a defined type implements the interfaces its methods satisfy #2419 changed Go resolution.Flows stay connected. On main, the stub-bound handler calls reached implementations through the name-only
go-grpc-stub-implbridge; now they land on the interface method and rideinterface-impldispatch (_KV_Range_Handler → KVServer.Range → kvServer.Rangeinv3rpc/key.goincodegraph_explore's Flow). For every moved site I compared the implementations its target dispatches to in one hop: those reached before but not after are 415 on etcd and 18 on grpc-go, and none is an implementer of the interface the call lands on. They are clients (retryKVClient,Election), server-to-client adapters (kvs2kvc,es2ec), printers, mocks andEtcdServer(still reached one hop later, throughkvServer); none embeds anUnimplemented…/Unsafe…Serveror has the interface's method set.Cost: with each file's lines loaded (the resolver's other Go checks read the same lines), the check costs about what reading the ref's line does: 5–70 ms per pass over every Go call ref of these repos (13k–97k refs).
Suite: the box ran at 100% CPU with 100–220 other node processes throughout. In parallel, 104 of 6,684 tests failed (100 timeouts, 2 EBUSY/EEXIST teardowns, a writer-lock state check and a parse-time bound). A rerun of those 40 files with 3 workers and long timeouts left 3 (daemon-pid-reuse EBUSY, function-ref's Python-only #1820 case against its own 60 s limit, mcp-writer-lock), and those 3 files pass alone. tsc is clean.
Not in this PR
v, ok := x.(T)thenv.M(), still has no inferred type: Go's receiver patterns read:=composite literals,varand parameters, not assertions. About 90 such locals across these four repos (49 in grpc-go).type MyServer pb.KVServer) has that interface's methods; an assertion to it finds no method of its own and links nothing. Not seen in these repos.🤖 Generated with Claude Code