Repository navigation
fix(go): a call further along a chain links the method of the type the chain reaches - #2782
Merged
Merged
Conversation
…e chain reaches
A Go method called on a receiver the extractor keeps no name for, such as
a fluent chain's later link (model.WithContext(0).Where(1).First()), a
call on what a function returns (time.Now().UTC().Format(...)) or a
composite literal (GitOracle{}.Worktree()), reached the resolver as its
bare name at the column where the chain starts. Name matching then took
a project method of that name by proximity, or the only one: the issue's
GORM-style Where and First went to another query type, the standard
library's Format to the one project Format, and a literal's method to a
namesake type.
A new Go resolver step reads the chain at that column and types the
receiver link by link: a call's result, or the one a multi-value
assignment takes; a conversion's or a composite literal's type; a
generic call given its type arguments; an embedded field by its type's
name; a package-level variable from the call it is set to. A result
written with type parameters keeps them as parameters, read from the
declaration, since the indexed signature leaves them out, and the helper
that reads them now includes a one-line function's only line. The method
is the one Go selects on that type. A type from outside the project, or
one that can't be read, links nothing instead of a guess. When a chain
calls one name twice, the bare ref is the later call.
Validation: 8 new tests; 7 fail on main, and the repeated-name case
fails under a first-link rule; the 43 Go test files pass. A/B against
main c326b6b, edges removed/added: gin 80/72, etcd 1726/1327,
prometheus 2249/1265, sonic (gorm/gen) 910/877, kubernetes 88288/84331.
Every removed edge was a name-match guess except 173 in kubernetes: 171
are the same edge now owned by another ref of its chain, and 2 wrong
links of metav1.Now().Time.Unix(). Sampled retargets, drops and
additions read correct; express and flask dumps are byte-identical.
Indexing cost is within noise, median of 3 interleaved runs, wall/CPU
seconds main -> fix: etcd 9.5/28.6 -> 8.6/25.4, prometheus 10.5/42.1 ->
10.5/42.6, kubernetes 121.9/413.7 -> 121.3/416.8.
Fixes #2543
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
colbymchenry
added a commit
that referenced
this pull request
Oct 28, 2026
…its type's method (#2961) A Go method call on a local bound from a call or a conversion, like `r := newRing(); r.Reset()`, `v1 := router.Group(...); v1.GET(...)` or `c, r := CreateTestContext(w); r.Use(...)`, was matched by the method's name alone: inferLocalReceiverType reads a declaration's written type and a few line patterns, none of which reads a call's result. The by-name guess took a namesake (etcd's `lg := s.Logger()` linked `lg.Info(...)` to a project logger's Info, prometheus's `NewWriteHandler(...)` results to the read handler's ServeHTTP), or found nothing. Main already types a value from its declaration for indexed and chained calls (goDeclaredValueType, #2782). Plain receivers now use it too: a local whose in-scope declaration has a call or a conversion as its value takes that value's type (the k-th result of several, a method's result, a conversion's type), read in the file that writes it (GoReceiverDecl.file), for matchMethodCall, matchMemberFunctionRef and matchGoFieldChainCall. A value of a type from outside the project links nothing, including one a project function returns as an outside interface. Assertions stay with goAssertedLocalType, and a type switch's guard is not typed from its value. The typer itself, for every path that uses it: - a trailing `.(T)` is a T whatever its prefix (`pool.Get().(*Conv)`); - a call through a func-typed field gives the field's result type; - a bare call a dot import brings in is typed (`package watch_test` calling `NewStreamWatcher(...)`); - a method of a struct or interface literal, of an outside generic type (grpc's `ServerStreamingClient[Resp].Recv()`) or one the index doesn't show on a project type gives an unknown type, not "outside", so name matching runs as before. Validation: - go-receiver-type.test.ts (the PR's 23 cases, 2 expectations updated where the call is now typed, plus 4 ours), go-std-method-receivers.test.ts and go-chained-receivers.test.ts: on main 19 of 51 fail; all pass here. All 45 go-*.test.ts files pass (491 tests). - A/B vs main 2577601 (same kernel): gin +194/-52/65 moved, etcd +4,441/-359/743, prometheus +7,041/-239/1,409, a kubernetes staging subtree +1,154/-86/354, harbor without its portal +423/-138/366. express and flask byte-identical. Sampled additions right; removals are outside values (zap, os, grpc, otel, beego, pflag) and the outside- interface results, about 80% of which were wrong by sample. Known loss: a type defined over an interface (`type Codec Serializer`) has no methods in the index, so a coincidental guess on such a value goes, as in #2706. - Compared with PR #1954 on the same repos: 4,962 more links and 360 of its edges gone, those mostly wrong guesses (166 of them the outside- interface results); its declaration-line restriction also lost right links on range variables and field values, which this leaves to main's scan. - CPU (profile self time of receiver typing): prometheus 1.82 -> 1.72 s, k8s subtree 0.88 -> 0.92 s, harbor 1.02 -> 0.86 s. Reimplements PR #1954 by @danusha2345 on main's value typer; its tests are included. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
In Go, a method called on a receiver the extractor keeps no name for now links to the method of the type that receiver has, or to nothing. That covers a fluent chain's later links (
model.WithContext(0).Where(1).First()), a call on what a function or method returns (time.Now().UTC().Format(…),query.Use(db).User.Where(…)), a composite literal (GitOracle{}.Worktree()), a conversion ((*store.Response)(nil).Header()) and a field reached further down a chain. Before, these calls arrived by their bare method name and name matching picked a project method of that name: the issue's GORM-styleWhereandFirstwent to another query type, a standard-library chain'sFormatto the one projectFormat, and a literal's method to a namesake type.Fixes #2543.
Reproduction (issue script, main
c326b6bevs this branch)FindQuery()mysql/conn.gofunctionQuery(already fixed on main).Where(1)CardDo::Where(query/card.go)SessionDo::Where(query/session.go).First()CardDo::First(query/card.go)SessionDo::First(query/session.go)The three shapes in @inth3shadows's comment:
GitOracle{}.Worktree()FakeOracle::WorktreeGitOracle::Worktreeout[0].Total()RepoCount::Total(fixed by #2755)time.Now().UTC().Format(…)Report::FormatRoot cause
The extractor keeps a Go call's receiver only for
x.M(),a.b.M()andF().M()with an identifierF. Every other link of a chain reaches the resolver as the bare method name, at the column where its chain starts, and the name matcher then chose among all same-named methods by proximity, or took the only one outright.Fix
A new Go step in
resolveOneInner, after the type-assertion and indexed-element steps, reads the chain at the ref's column in the file's blanked source and types the receiver link by link (matchGoChainedCallinname-matcher.ts):_, txn := store.Begin()), with a package's declarations required to agree;T(x)/(*pkg.T)(x)and a composite literalT{…}/pkg.T{…}/List[int]{…}give their type, and a generic function called with its type arguments (validate.PtrMapNoNils[string, string](…),sets.New[string](…)) gives its result;[T any]out:Tis no package's type (a namesaketype Tis not linked),V Validatorhas its constraint's methods,Set[T]is aSet. Reading a one-line generic function's type parameters also needed the helper to include its only line;client.KV.Get), and a package-level variable is typed from the call it is set to (var localScheme = scheme.New());A chain whose type comes from outside the project links nothing, and so does one whose type can't be read (a func value's call, a result written with type parameters, an anonymous struct, a function-local type): guessing by the name is what produced the wrong edges. When a chain calls the same name twice, the bare ref is the later call (the first, if the extractor keeps it, arrives under its own name).
The change is resolver-only and shared by both extractors; the new tests pass with
CODEGRAPH_KERNEL=0and with the native kernel.Validation
New
__tests__/go-chained-receivers.test.ts(8 cases: the issue's chain, composite literals, standard-library chains, call/multi-value/conversion/embedded-field typing, a package-level variable, generic calls and literals, a repeated name in one chain, receivers that can't be read). 7 of the 8 fail on main; the repeated-name case passes there by name matching and fails under a first-link rule. Mutating the last-link rule, the type-argument handling, the result's type parameters or the one-line head each fails its own case. The 43 Go test files pass (463 tests).A/B, main
c326b6bevs this change (no Go, JS or Python indexing change betweenc326b6beand the branch base88ebefa1, whose new commits are upgrade and Pascal fixes):0f09c3a9bf9cadf0017fcf2e53db5562(gorm/gendal/)006cdf870dee2c52(control)d086db85(control)Nodes are identical in every repo. Every removed edge was a name-match guess (
exact-match) except 173 in kubernetes: 171 are the same edge now owned by another ref of its chain (a repeatedfldPath.Child(…).Child(…)), and 2 import-resolved links ofmetav1.Now().Time.Unix()to the package functionmetav1.Unix; every added edge is a typedinstance-methodlink.Triage (seeded samples, source read for each):
attachmentDo::Whereto each table's ownxxxDo::Where/First/Delete, andxerr.WithStatus(…).WithMsg(…)toutil/xerrinstead ofutil/haloerr; kubernetes' scheduler test wrappers (st.MakePod().Name(…).Obj()), client-go typed clients (client.CoreV1().ConfigMaps(ns).Delete),field.NewPath(…).Index(0),ktesting.Init(t).SyncTestin the package the file imports.time.Time, gorm/gen's embeddedgen.DO.Count, a method from an embeddederror) were all wrong before. Of the "can't be read" drops, a sample of 20 in kubernetes had 6 correct-by-luck edges (anonymous-struct test tables, function-local types, func-valued package variables) and 14 wrong ones.rh.lister.ControllerRevisions(ns).Get,v1.ResourceMetricStatus{}.OpenAPIModelName(),fields.Set{…}.AsSelector().String(), gorm/genDelete/Create/Order).Cost, median of 3 interleaved
codegraph initruns per arm (wall / CPU seconds; the machine was shared, so kubernetes runs spread over 99–136 s on both arms):Full suite on the pushed commit
a24619cf(CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4, Windows): 708 files, 8,445 passed, 0 failed, 82 skipped.Not covered
F[int](x), links no edge toFitself on main or here (the extractor emits no ref for it); the method called on its result now links.var GetX = gauge.get,initTest := testutils.InitTest…), an anonymous struct (test tables), or a function-local type still links nothing.No contributor PR for this issue; @inth3shadows's comment supplied the composite-literal and standard-library cases, both covered by the tests.
🤖 Generated with Claude Code