Repository navigation
fix(go): a call on a package-level variable or constant links through the type it is declared with - #2667
Merged
Conversation
… the type it is declared with
Nothing read the declaration of a Go variable or constant declared at the
top level of a package (the scope reader skips them, and the pattern scan
reads only the calling function), so a method call through one was matched
by the method's name: etcd's `var lg *zap.Logger` made `lg.Panic(...)` a
project logger's `Panic`, prometheus's protobuf `xxx_messageInfo_*` values
reached project `Marshal` methods, and kubernetes' registry tests' calls on
each package's `var Strategy = fooStrategy{...}` reached another package's
exported `Strategy` type.
matchGoPackageValueCall reads a same-package `variable` or `constant` node's
spec when the call's receiver is a plain name that no local or parameter
binds and no import names: a written type, a composite literal's or a
conversion's type, or for a constant alone in a `const ( ... )` group the
spec above it. A project type gives its own method (0.9) through the
package that declares it; a type or value from outside the project, a
predeclared type or a type literal gives nothing. A value a project
function returns, an untyped constant, declarations that differ between
build-tag files, and a project type whose methods aren't found are left to
the strategies below. A `package foo_test` file in the directory is another
package.
A/B on main 90d6501, same kernel for both arms, full index per arm:
- gin: 4 guesses now typed at 0.9.
- prometheus: 59 dropped (gogo-protobuf `xxx_messageInfo_*`, `sync.Pool`
values), 40 new links (`RoleEndpoint.String()` and its siblings).
- etcd: 18 dropped (`var lg *zap.Logger`, a cobra command), 43 new links
(`var display printer = &simplePrinter{}`), 3 now typed.
- harbor: 2 dropped (`var handler http.Handler`), 24 new, 38 retargeted
(`var configManager *cfgPkg.CfgManager`), 34 now typed.
- kubernetes: 46 dropped (outside-typed values), 260 new, 405 retargeted
(each registry package's own strategy type), 430 now typed.
fmt, llmc and typeorm are identical. Every dropped link, grouped by
receiver, and every retargeted owner pair was checked against the
receiver's declaration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 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
Follow-up to #2664. Nothing read the declaration of a Go variable or constant declared at the top level of a package. A method call through one was therefore matched by the method's name alone:
var lg *zap.Logger, thenlg.Panic(…)in another file of the package, linked to a project logger'szapRaftLogger::Panic;var xxx_messageInfo_Sample proto.InternalMessageInfolinked toSample::Marshal;RoleEndpoint.String(), whereRoleEndpoint Role = "endpoints", linked to another package'sRole. Since fix(go): a call of a standard method name likeerr.Error()links to its receiver's declared type, never to one it shares a word with #2664 that call links to nothing, because the receiver isn't named after its type.#2664's scope reader skips these declarations on purpose ("outside every block it is a package-level name"), and the pattern scan only reads the calling function.
Fix
The change is
matchGoPackageValueCallinsrc/resolution/name-matcher.ts, Go only. It applies to a callx.m()wherexis a plain name, the file imports no package by that name, and no local or parameter binds it where the call is. Then avariableorconstantnode of the same package decides. Same package means the same directory and the samepackageclause, because apackage foo_testfile shares the directory.Its spec is read, in its own file:
var lg *zap.Logger,RoleEndpoint Role = "endpoints");var r = &Registry{});var p = Role("pod"));const ( … )group, the spec above it, which Go repeats (KindBafterKindA Kind = iota).A project type gives its own method (or one its embeddings promote) at 0.9, through the package that declares it, read with the declaring file's imports.
These link to nothing: a type from outside the project, a predeclared type, a type literal, or a value an outside package hands out (
var buf = bytes.NewBuffer(nil),var re = regexp.MustCompile(…)).Calls the reader leaves alone. These go on to the strategies below, as before:
var testParser = parser.NewParser(…)), which says no type;runtime.Codec, defined over an interface).Typing a value from a call's result is fix(go): type receivers from package-qualified parameters and call results; no guess for outside types #1954's subject.
Tests
__tests__/go-package-value-receivers.test.tshas 8 cases:runtime.Codecfallback: only the control fails.Full suite on a6cfb52 plus this change: 610 files and 7,461 tests passed, 0 failed.
A/B
The two arms are main 90d6501 and this change, with the same kernel binary. #2665 (TS/JS barrel imports on sync) landed after the A/B and touches no Go path. The branch is rebased onto it (a6cfb52), and the tests ran there. Each repo got a full
init -yper arm, and I diffed natural-key edge sets. The clones are the same ones #2664 used.Triage
I checked every dropped link, grouped by receiver, and every retargeted owner pair against the receiver's declaration in the source.
Dropped (125). Each is a value of a type from outside the project that main had bound to a project method by name:
var xxx_messageInfo_Sample proto.InternalMessageInfo, which reached projectMarshalandMergemethods;sync.Poolvariables;var noReferenceReleases = promauto.NewCounter(…), which reachednoopMetric::Inc;regexp.MustCompileresult.var lg *zap.Logger(15), which reachedzapRaftLogger::Panic/Debug;var rootCmd = &cobra.Command{…}(3), which reachedmigrationPlan::Execute.var handler http.Handler, which reached the registryctl blob handler.var realClock = &clock.RealClock{}(13), which reached another package's privaterealClock::Now;Cmd…commands (13), which reachedServerRunOptions::Flags;sync.Once,sync.Poolandatomicvalues;Fakeschemas;var decode cbor.DecMode;time.Date(…);checker.CostEstimate{…};template.Must(…)result.Retargeted (443).
var Strategy = fooStrategy{…}andvar StatusStrategy = …. Main boundStrategy.PrepareForUpdate(…)in those packages' tests, through the qualified name, to podcertificaterequest's exportedStrategytype. Now they reach each package's own strategy type.var SchemeGroupVersion = schema.GroupVersion{…},var PluginHandler = &RegistrationHandler{},var SimpleNameGenerator NameGenerator = simpleNameGenerator{},var featureGate featuregate.FeatureGate.var configManager *cfgPkg.CfgManager(24, was thelib/configManagerinterface by name),var authHelper *Auth,var helper = &authproxy.Auth{}.New links (367).
RoleEndpoint.String()and its five siblings reach the discovery package's ownRole::String. Since fix(go): a call of a standard method name likeerr.Error()links to its receiver's declared type, never to one it shares a word with #2664 these had linked to nothing; on earlier main they reached aws'sRole.var display printer = &simplePrinter{}givesprinter::Alarmand others (43).SeverityandStatusconstants.var obj = &resource.ResourceClaimTemplate{…}thenobj.DeepCopy(). Inobj := obj.DeepCopy()the right-hand side is the package'sobj, and the scope reader agrees.Same target, now 0.9 (471). Main's guess was right, and the declaration now confirms it.
Kept on purpose.
var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{}is declared asruntime.Codec, a type defined over an interface whose methods its node doesn't carry. A first version linked those 6 calls to nothing. Now a project type with no method found leaves the call to the strategies below, which link it as before. The test pins this (JSONCodec).var testParser = parser.NewParser(…)) are untouched. They're fix(go): type receivers from package-qualified parameters and call results; no guess for outside types #1954's subject (open, call-result typing).🤖 Generated with Claude Code