Repository navigation
fix(go): a call through a parameter or local named like a standard-library package reaches the project's method - #2489
Merged
Conversation
…brary package reaches the project's method isBuiltInOrExternal dropped every dotted Go ref whose first segment is in GO_STDLIB_PACKAGES before any resolution, so a call through a parameter or local of that name linked to nothing: gin's `context.AbortWithError(…)` in `func(context *Context)`, harbor's `context.IsAuthenticated()` after `context := NewSecurityContext(…)` and `log.Errorf(…)` after `log := log.G(ctx)`, etcd's `printer.DBHashKV(…)` and `trace.Step(…)`. Such a call now gets past the package list when #2448's scope reader finds the name declared in the function around it, unless the declaration says it holds no project method: a type from outside the project or a predeclared one, or what an outside package's function hands out (`scanner := bufio.NewScanner(r)`, `url, err := url.Parse(raw)`). #2478's exemption for locals bound from a type assertion stays beside it. matchMethodCall resolves these calls by the declaration alone, never by the receiver's name, which fits dozens of a big tree's types (kubernetes' `printer := NewTablePrinter(…)` is no kubeadm `Printer`): - a written or asserted project type (goWrittenType) calls its own method, an interface's or a promoted one, or nothing; - a value a project function hands out calls a method of that function's package: the type named like the variable, or the only one of the name; - anything else calls only a method no other type declares, never one with a standard-library method name. The scope reader also records `x := T{…}` / `new(T)` types and the own-package function a `:=` calls. GO_STDLIB_PACKAGES moves to name-matcher. A/B against main 1d3619d, natural-key edge diffs with identical nodes and no edge removed: gin +2, prometheus 0, etcd +44, harbor +60, kubernetes +508, every one triaged correct. The first version, which let these calls reach name matching, added 811 on kubernetes, 346 of them guesses by the receiver's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bompus
added a commit
to bompus/codegraph
that referenced
this pull request
Oct 11, 2026
* fix(go): a call through a parameter or local named like a standard-library package reaches the project's method (colbymchenry#2489) isBuiltInOrExternal dropped every dotted Go ref whose first segment is in GO_STDLIB_PACKAGES before any resolution, so a call through a parameter or local of that name linked to nothing: gin's `context.AbortWithError(…)` in `func(context *Context)`, harbor's `context.IsAuthenticated()` after `context := NewSecurityContext(…)` and `log.Errorf(…)` after `log := log.G(ctx)`, etcd's `printer.DBHashKV(…)` and `trace.Step(…)`. Such a call now gets past the package list when colbymchenry#2448's scope reader finds the name declared in the function around it, unless the declaration says it holds no project method: a type from outside the project or a predeclared one, or what an outside package's function hands out (`scanner := bufio.NewScanner(r)`, `url, err := url.Parse(raw)`). colbymchenry#2478's exemption for locals bound from a type assertion stays beside it. matchMethodCall resolves these calls by the declaration alone, never by the receiver's name, which fits dozens of a big tree's types (kubernetes' `printer := NewTablePrinter(…)` is no kubeadm `Printer`): - a written or asserted project type (goWrittenType) calls its own method, an interface's or a promoted one, or nothing; - a value a project function hands out calls a method of that function's package: the type named like the variable, or the only one of the name; - anything else calls only a method no other type declares, never one with a standard-library method name. The scope reader also records `x := T{…}` / `new(T)` types and the own-package function a `:=` calls. GO_STDLIB_PACKAGES moves to name-matcher. A/B against main 1d3619d, natural-key edge diffs with identical nodes and no edge removed: gin +2, prometheus 0, etcd +44, harbor +60, kubernetes +508, every one triaged correct. The first version, which let these calls reach name matching, added 811 on kubernetes, 346 of them guesses by the receiver's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): drop the Go entries the merge listed twice and the log example --------- Co-authored-by: Colby Mchenry <me@colbymchenry.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 11, 2026
…o guess for outside types Two receiver shapes still got no type, and a receiver typed outside the project still got a guess by method name: - A parameter or receiver of a package-qualified type (`s *store.Store`). - A receiver bound to a call: `r := newRing()`, `s, err := store.NewStore()`, `if r := find(id); r != nil`, a call spread over several lines, a conversion (`list := model.BotList(bots)`). The type is the callee's first result as its signature spells it, read in the callee's file. Only when the call is the whole right-hand side, and not when it takes the receiver itself. A receiver whose declared type comes from a package outside the project's modules (`conn net.Conn`, `ctx context.Context`, `req *http.Request`, an alias of one, the result of an outside function) gets no edge: `ctx.Done()` went to the one project type declaring a `Done`. A project function's result declared as an outside type by value (`http.RoundTripper`) is left as it was, since that is usually an interface a project type implements. Aliases of project types are followed by colbymchenry#2417, which gave them nodes; the alias reader this change carried is gone, and an alias of an outside type is recognized from that node's declaration. A variable named like a standard-library package (`ring`, `token`, `parser`) is colbymchenry#2489's now: this change carried its own rule for it (skip only when the file imports the package), which is dropped in favour of upstream's `isGoProjectLocalCall`, and the standard-library package check is upstream's again, untouched. Resolver only: no extraction change, the receiver scan keeps its per-line memo. 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
isBuiltInOrExternaldrops every dotted Go ref whose first segment is inGO_STDLIB_PACKAGES(context,log,path,user,token,printer,trace,plugin, …) before any resolution runs. That is right forfmt.Printlnandcontext.Background(), but a parameter or local can carry those names too, and a call through it is a method call on the variable:context.AbortWithError(…)insiderouter.Use(func(context *Context) {…})(gin's ownContext)context.IsAuthenticated()aftercontext := NewSecurityContext(…), andlog.Errorf(…)afterlog := log.G(ctx)printer.DBHashKV(…)afterprinter := initPrinterFromCmd(cmd), andtrace.Step(…)aftertrace := traceutil.Get(ctx)All of these linked to nothing on main.
What changes
The filter (
index.ts). A call is still taken for the package unless its receiver is a parameter or local of the function around it at the call: the scope reader from #2448 (goLocalDecl) decides. It stays dropped when what the declaration says holds no project method: a type from outside the project, a predeclared one, or what an outside package's function hands out (scanner := bufio.NewScanner(r),url, err := url.Parse(raw), acontext *gin.Contextparameter). #2478's condition for locals bound from a type assertion stays as it was, beside this one.Resolution (
matchMethodCall). A call that got past the filter this way is resolved by its declaration only, never by the receiver's name:goWrittenType, from fix(go): a call through a local bound from a type assertion reaches the asserted type's method #2478) calls its own method, an interface's, or one embedding promotes into it, or nothing (acontext PostStartHookContextgetsDonefrom thecontext.Contextit embeds);x := pkg.F(…),x := newFoo(…)) calls a method of that function's package: the one of a type named like the variable (printer := initPrinterFromCmd(cmd)is aprinter), or the only one of its name there;The scope reader now also records
x := T{…}/x := new(T)types and the own-package function a:=calls (GoDecl.callee).GO_STDLIB_PACKAGESmoved toname-matcher.tsunchanged, because both files need it.Why the receiver's name never picks a type. The first version simply let these calls reach name matching. On kubernetes that added 811 edges. 346 of them were guesses by the receiver's name: 212 by a shared word, 129 by a type named like the capitalized receiver and 5 by the exact name. Most of those I read were wrong, because names like
plugin,printer,pathanduserfit dozens of types in a big tree:printer := NewTablePrinter(…)went to kubeadm's unrelatedPrinterinterface;plugin, err := pluginMgr.FindPluginByName(…)went to whichever of forty…Plugintypes came first;path, err := LookupSchemaForField(…)thenpath.GetPath()went tohostPath::GetPath;func(context genericapiserver.PostStartHookContext)thencontext.Done()went towait.channelContext::Done.With evidence-only resolution, kubernetes adds 508 edges. That includes 43 the first version had sent elsewhere, such as
printer := NewEventPrinter(…), which now reachesEventPrinter::PrintObj.Validation
Natural-key edge diffs, main 1d3619d vs this branch, same native kernel in both arms. Node rows were identical and no edge was removed in any repo.
Every added edge was triaged. A script checked each edge against the declaration at its site, and every edge the script couldn't confirm was read by hand, along with samples of those it did:
PreEnqueuePlugin→Plugin::Name,RuntimeService→ContainerManager::ListContainers,upgradePlanTextPrinter→TextPrinter::Fprintln. The rest are parameters on multi-line signatures, and a parameter that a loop-local of the same name doesn't shadow outside the loop.ctx, trace := traceutil.EnsureTrace(…)takes*Trace). The rest were read:trace(25) andprinter(2);log := log.G(…)(31) reachesLogger, andcontext := NewSecurityContext(…)(19) reaches the concretesecret.SecurityContext, not thesecurity.Contextinterface or another package'sSecurityContext;field.NewPath,jsonpath.New,testPlugin,NewPlugin[…],GetTestKubeletVolumePluginMgrand others.hash := crc.New(…)returnshash.Hash32backed by&digest{}, so it reachesdigest::Sum32/Reset/…. kubernetes'admissiontesting.WithReinvocationTestingreachesreinvoker::Admit.plugin.host.GetMounter→VolumeHost,context.Path.String→field.Path,sync.kubeAPI.*onfunc (sync *NodeSync)receivers).path := restClient.Get()…→Request::Param,errors := ValidateX(…)→ErrorList::ToAggregate, etcd'stls := newTLS(…)→TLSInfo::ClientConfig, and harbor'scHash::toListrange value.The 44 added edges with a standard-library method name (
String) are all typed (path *field.Path) or field chains; none is a guess.Calls through such locals that stay unlinked:
bufio.Scanner,url.URL,hash.Hash32, gengo's*generator.Context,k8s.io/utils/trace,utilexecandmath/rand.Overlap
isGoAssertedLocalexemption is kept as its own condition. Asserted locals named like a stdlib package resolve through the samegoWrittenType, so the two agree.if. This PR covers parameters and locals, which fix(go): a project package named like a standard-library package is not the standard library #2368 doesn't. Whichever lands second keeps both conditions.user.Username) stay dropped, as fix(go): a dotted call is written through its own receiver, and a local named like an import is that variable #2448 does for locals named like an import, since a selector value can't be told from a field read.Performance
The filter now reads the scope of files whose only dotted calls go through standard-library packages. Measured with interleaved full indexes, total process CPU, main vs this branch:
Each difference is inside its own run-to-run spread (etcd main ranged 33.3–35.5 s).
Test plan
__tests__/go-stdlib-named-locals.test.ts(new, 9 cases): 5 fail on main (the positive cases), and all 9 pass with the fix, on both the native kernel and wasm (CODEGRAPH_KERNEL=0).extractionandframeworks-integration: 25 files and 953 tests on the kernel path; the Go suites on wasm: 22 files and 211 tests.tsc --noEmit,npm run build🤖 Generated with Claude Code