Skip to content

fix(go): resolve field-chain calls on unexported method receivers - #2325

Closed
GoDiao wants to merge 1 commit into
colbymchenry:mainfrom
GoDiao:fix/go-unexported-receiver-field-chain
Closed

GoDiao wants to merge 1 commit into
colbymchenry:mainfrom
GoDiao:fix/go-unexported-receiver-field-chain

Conversation

@GoDiao

@GoDiao GoDiao commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #2323.

Problem

matchGoFieldChainCall infers the base receiver's type with inferLocalReceiverType. For a Go method receiver the only matching pattern was the typed-parameter one, which is PascalCase-guarded, so func (s *server) produced no type and s.service.AddItem() was dropped.

Change

One new pattern in the Go branch of localReceiverTypePatterns, anchored on func (:

new RegExp(`\\bfunc\\s*\\(\\s*${r}\\s+\\*?([A-Za-z_]\\w*)\\s*(?:\\[[^\\]]*\\])?\\s*\\)`)
  • Accepts unexported receiver types, since the func ( anchor already rules out the unrelated ident Type pairs the PascalCase guard protects against.
  • Skips a generic receiver's type parameter list (func (c *cache[T]) gives cache).
  • The existing guarded pattern is unchanged, so plain parameters behave as before.

The resolved method is still validated by resolveMethodOnType, as for every other inferred receiver.

Tests

Added a case to the Go field-chain receiver calls (#1276) block in __tests__/resolution.test.ts. It covers an unexported receiver (server) and a generic one (cache[T]); both fail before this change and pass after it.

npm test locally on macOS: everything passes except bundle-launcher.test.ts, which fails the same way on main on this machine (Library not loaded: libnode.127.dylib, a local Node install issue).

Impact on real repos

The numbers in #2323 were measured with this exact pattern added: +1,123 Go call edges on prometheus, +707 on etcd, +3,328 on harbor (src/) and +542 on mattermost (server/). A random sample of 32 new edges checked against the source was all correct. Only 2 existing edges changed, both in mattermost, and both were previously wrong (for example src.set in a fileSrc method used to resolve to jsonSrc::set).

…lbymchenry#2323)

The Go typed-parameter receiver pattern is PascalCase-guarded, so
`func (s *server)` yielded no receiver type and `s.service.AddItem()`
was dropped. Add a receiver pattern anchored on `func (` that accepts
unexported and generic receiver types; plain parameters keep the guard.
@GoDiao

GoDiao commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

A note on overlap: I found #1954 after opening this. Its new parameter/receiver pattern also matches func (s *server), so it covers this fix as part of a larger change. On the four repos from #2323, 97–100% of the edges this PR adds are also added by #1954.

Measured on the same shallow clones (#1954 at 0afc052, rebased on current main). "Dropped" means a call site that has an edge on main and none on the branch:

Repo Go call edges: main / this PR / #1954 Removed or dropped by this PR Dropped by #1954 Retargeted by #1954
prometheus 38,990 / 40,120 / 43,221 0 416 685
etcd 16,323 / 17,035 / 19,435 0 960 291
harbor src/ 21,656 / 24,985 / 25,141 0 261 701
mattermost server/ 122,459 / 122,999 / 150,692 2 4,441 2,080

#1954 finds many more edges. Among the call sites it drops, these shapes resolve correctly on main:

So the two can go either way: this PR is a small, removal-free subset that can land on its own, and #1954 would supersede it. Happy to close this one if you'd rather take #1954.

@danusha2345

Copy link
Copy Markdown
Contributor

Thanks a lot for measuring #1954 on these repos: the alias and embedded-struct cases were real losses of correct edges. Both are fixed on the branch now: aliases like type Context = web.Context are followed to the aliased type, and promoted methods are looked up through embedded structs in their own packages, with main's resolution as the fallback when an embedding can't be followed. On the same clones, call sites that have an edge on main and none on #1954 went from 4,441 to 1,277 on mattermost server/ and from 261 to 217 on harbor src/. A sample of what's left is almost all standard-library receivers (mu.Lock(), t.Logf, r.Context()). Whether to take this PR, #1954 or both is the maintainer's call; this one is the smaller change and removes no edges.

@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @GoDiao! Your receiver pattern landed in #2361, extended to plain parameters. One finding changed the shape of the fix. Applied alone on main, it sent many newly typed calls to another package's same-named type: 1,042 of the 1,998 new calls on harbor. So #2361 also looks a method up only in the package that declares the receiver's type, or in the types it embeds. You're credited as a co-author on the commit and in the changelog. Closing this one in favor of #2361.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go: calls through a struct field are not resolved when the receiver type is unexported

3 participants