Repository navigation
fix(go): a call of a standard method name like err.Error() links to its receiver's declared type, never to one it shares a word with - #2664
Merged
Conversation
… its receiver's declared type, never to one it shares a word with A Go call of a standard method name (GO_STD_METHODS: Error, String, Err, Lock, Len, Header, ...) on a receiver whose type wasn't worked out was guessed by a word the receiver shares with a type's name: kubernetes' `err.Error()` linked to `notRegisteredErr::Error`, prometheus's to `ParseErr::Error`, etcd's `leaderID.String()` to `MoveLeaderRequest`, and a receiver declared as a type from outside the project to a project type of its name (etcd's `var pendingMu sync.Mutex` to its concurrency `Mutex`). In matchMethodCall, for a standard method name on a plain Go variable: - The receiver's declaration in scope (goLocalDecl, the scope reader) decides, over the unscoped pattern scan: a project type's own method, through the package that declares it; nothing for a predeclared type, a type from outside the project, a type literal, a value an outside package hands out, or a declared type that lacks the method. - Otherwise a guess needs the receiver to name the owner type itself, case and a trailing number aside (`id` an `ID`, `urlsMap` a `URLsMap`): in Strategy 3 through receiverNamesOwner, and in Strategy 2, which took any method of the owner's file whose name contained the type's (`status` reached `RuntimeStatus::String` beside a `Status` type). - Only a declaration the scope reader finds rules a call out. The pattern scan reads back for the name without block scope and can find another declaration of it, like a test table's `version string` above `version, _ := parse(...)`; valid Go calls no method but `Error` on a predeclared type, so such a read says nothing. A/B on main b4e1013, same kernel for both arms, full index per arm: - gin: 17 guesses dropped; 14 calls newly typed (`c.String(...)` in `func(c *gin.Context)` handlers). - prometheus: 265 dropped, 89 newly typed, 6 retargeted. - etcd: 335 dropped (23 of them typed links of `sync.Mutex` and `sync.RWMutex` values to project namesakes), 52 newly typed, 7 retargeted. - harbor: 174 dropped, 6 newly typed, 6 retargeted. - kubernetes: 2489 dropped, 241 newly typed, 77 retargeted. No link of any other method name changed, and the fmt, llmc and typeorm controls are identical. Of 132 dropped links sampled, 3 were right (locals typed by a project call's result, which no reader types yet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
Merged
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 20, 2026
…he name strategies; colbymchenry#1938/colbymchenry#1954 tests follow upstream's colbymchenry#2684/colbymchenry#2664 The same changes went into the rebased PR branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 20, 2026
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
A Go call of a standard method name (
GO_STD_METHODS:Error,String,Err,Lock,Len,Header,Equal, …) on a receiver whose type wasn't worked out was guessed by a word the receiver shares with a type's name. That word is what everyerrshares with an…Errtype, and everyctxwith avalCtx. On main b4e1013:err.Error()calls link tonotRegisteredErr::Error, prometheus's toParseErr::Error, gin's to a test'sTestErr::Error, and etcd's toErrKeepAliveHalted::Error;leaderID.String()links toMoveLeaderRequest::String;var pendingMu sync.Mutexreaches its ownconcurrency.Mutex, and harbor'sw http.ResponseWriterreaches itsResponseRecorder.A receiver declared with a predeclared type (
err error) fell through to the same guesses.Fix
The change is in
matchMethodCall(src/resolution/name-matcher.ts). It applies only to Go, and only to a standard method name called on a plain variable.goLocalDecl, takes precedence over the unscoped pattern scan.err error);var mu sync.Mutex,tb testing.TB);ctx, cancel := context.WithTimeout(…),endpoint := &url.URL{…});idnames anID,urlsMapaURLsMap).receiverNamesOwner, in both its unique-candidate (0.7) and its scoring (0.65) branch.statusreachedRuntimeStatus::Stringbeside aStatustype.version, _ := parse(…)came out as the test table'sversion string. In prometheus,var ( query promql.Query )is in avarblock the scan doesn't read, so it came out asquery string. Valid Go calls onlyError()on a predeclared type, so a predeclared type read that way says nothing.Two refactors keep behavior the same:
isGoOutsideImportis extracted fromisGoExternalQualified, andnamesOwnerType/flatWordare extracted fromsharesReceiverWord. Every other language's path is unchanged.Tests
__tests__/go-std-method-receivers.test.tshas 16 cases:versionText, which main already links right.runQueries,versionText) fail. That version trusted the pattern scan's predeclared type, and the A/B below caught it dropping 57 links of other method names, like kubernetes'version.Major()and kubeadm'sdryRun.Client().A/B
The two arms are main b4e1013 and this fix, with the same kernel binary. #2662 landed after the A/B. It only changes JS/TS calls whose method name has a
$, which Go calls never take. The branch is rebased onto it (0d33925), and the tests above ran there. Each repo got a fullinit -yper arm, and I diffed natural-key edge sets. Clones: gin 43fe48e8, prometheus 39c878f0, etcd 76d58e32, harbor b323c0c8, kubernetes 4c05e667.Only standard method names change. No edge of any other method name changed in any repo. Node sets are identical everywhere.
Controls are identical. fmt (C++), llmc (C/CUDA/Python) and typeorm (TypeScript) have identical node, edge and unresolved-ref sets.
Cost: none measurable. Kubernetes index wall time, interleaved on the same box:
The differences are within run-to-run noise.
Triage
Dropped guesses. These are 0.65 and 0.7 links, plus Strategy 2's 0.8 ones. I judged a seeded random sample of 132 drops (kubernetes 40, prometheus 25, etcd 25, harbor 25, gin 17) against each receiver's declaration. 3 were right on main:
buffer.Header(), afterbuffer := lib.NewResponseBuffer(w);jStatus.String()(twice), afterjStatus := job.Status(…).Each is a local typed by a project function's result or a conversion, which nothing types today (see Overlap). The other 129 were wrong:
err.Error()on plainerrorvalues;c.String(0)onrandfill.Continuereaching a projectC,endpoint := &url.URL{…},queue *buffer.Ring;var quotaCmdLock sync.MutexreachingStripedLockSet::Unlock,errBrokeResumable = errors.New(…)reachingErrKeepAliveHalted::Error;expr.String()on aparser.ExprreachingStepInvariantExpr::String, andresult.String()on abytes.Bufferreaching promql'sResult::String.I also checked every Strategy 2 drop in prometheus (18) and the kubernetes ones by target (43). All were wrong.
Typed links dropped. etcd 23, harbor 7, kubernetes 8. Every one is a type from outside the project bound to a project namesake:
var nowMu sync.Mutex,mux := sync.RWMutex{}andvar pendingMu sync.Mutex, which reached itsconcurrency.Mutexandrecipes.RWMutex;var openAPIErr openapi.Error(go-openapi), which reached harbor's ownError::Error;w http.ResponseWriter, which reached itsResponseRecorder;var s fieldpath.Set,var remoteIP net.IP(which reached a CELIP) andvar lookback time.Duration(which reachedmetav1.Duration).Retargeted.
iter.Err()→chunks.Iterator::Err(wasStringIter::Err), andexpr.String()→Node::String;server.Lock(…)→LockServer::Lock(waslockServer::Lock);resource.String()→types.Resource::String(wasResourceMetadata::String);a.String()onfunc(a, b labels.Selector)→Selector::String(wasResourceName::String).requirement.String()→Requirement::String(wasRequirements::String);entry.String()→ipset.Entry::String.metric.String()on prometheus client_model'sMetric).result := ValidateAndReturnVisualize(…)returns aRobustnessResult. The exact owner now picksResult::Error, whichincludes()had gotten right by file order.Newly typed. Calls like these now link through their declared type:
c.String(…)infunc(c *gin.Context)handlers;ss.Err()→SeriesSet::Errandlbls.String()→Labels::String;localID.String()on alocalID types.IDparameter.Among the 241 kubernetes ones, a sample of 20 checked out right.
Remaining standard-name guesses are receivers named after their own type:
overlaps.String()→Overlaps::String,urlsMap.String()→URLsMap::String,severity.String()→Severity::String. One exception: in kubernetes, sixip.Equal()calls on values a function returns, probablynet.IP, still reach CEL'sIP::Equalbecause the variable is namedip. Typing a local from a call's result (#1954) would settle those.Overlap
buffer.Header()andjStatus.String(), and etcd'sresult.Error().isGoExternalQualified(isGoOutsideImporthere,isGoExternalImportthere) and touch nearby code inmatchMethodCall. Whichever lands second converges on one helper.decl.raw, which comes from the pattern scan. This PR's A/B shows that scan lands on table-test fields and on outer parameters of the same name. My first version stopped on such a read, and it dropped 57 valid links. That's why this PR rules a call out only on a declaration the scope reader finds.goLocalValuereads the same declarations, but only for receivers named like a standard-library package (context,printer). That path runs before this one, so the two never handle the same call.Follow-ups (separate PRs)
Package-level variables and constants as receivers. I'll do this next, in its own PR. Nothing reads a package-level declaration's type for a receiver today:
var lg *zap.Logger, thenlg.Panic(…), links tozapRaftLogger::Panic;var xxx_messageInfo_Sample proto.InternalMessageInfolinks toSample::Marshal;var c *cachelinks to a test suite's method, at 0.9.Calls through a receiver that is a
varorconstof the caller's own package: prometheus 154 linked and 129 unresolved, etcd 28 and 193, harbor 122 and 141.Outside-typed receivers for every method name, and locals typed by a call's result. Open fix(go): type receivers from package-qualified parameters and call results; no guess for outside types #1954 covers both, so I'm not duplicating it here. Sampling main's non-standard-name guesses with each receiver's declaration, such receivers sit behind about 1 in 8 kubernetes guesses (
logger klog.Logger,w := httptest.NewRecorder()) and 1 in 4 etcd ones (lg *zap.Logger,ctx context.Context,conn, err := grpc.NewClient(…)).🤖 Generated with Claude Code