Repository navigation
x/tools/gopls: references miss uses in a package's _test.go files until a file of the package is edited #81811
Description
gopls version
golang.org/x/tools/gopls v0.23.0 (h1:Dn6mf9WXu9iLnTftDDMb9wV0c6Se7PjzEMqP0LEe08Y=), built with go1.27.1.
v0.23.0 is still the newest gopls release (checked 2026-09-28); reproduces on it again in 3 of 3 runs.
Same result with gopls v0.20.0.
go env
GOARCH=arm64
GOOS=darwin
GOVERSION=go1.27.1
GOFLAGS=-modcacherw
GOTOOLCHAIN=local(macOS 27 on Apple silicon; path variables left out.)
What did you do?
I drive gopls over LSP from a small script. charmbracelet/bubbletea at 657aaae216e3f87df09b60959a9fe5de7b2ca419. The repository root is module charm.land/bubbletea/v2 (package tea); examples/ is a separate module (module examples) with replace charm.land/bubbletea/v2 => ../. There is no go.work. gopls is started at the repository root and creates two views, one for the root and one for examples.
The request is textDocument/references on Model in examples/http/main.go line 38, column 43 (1-based; LSP {line: 37, character: 42}):
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {Two runs, each with a newly started gopls, the same files on disk, and the same three files open at the end (examples/http/main.go, examples/set-window-title/main.go, mouse.go):
- A: open
examples/http/main.go,examples/set-window-title/main.goandmouse.go(a file of packagetea), wait until gopls is idle, send the request. - B: open
examples/http/main.goandtermios_unix.go; intermios_unix.gotype(after the)that closes the import block (line 9, column 2; LSP{line: 8, character: 1}); closetermios_unix.gowithout saving. The steps were sent about 200 ms apart. Then openexamples/set-window-title/main.goandmouse.go, wait until gopls is idle, send the request.
What did you see happen?
A returns 80 locations: 70 in examples/, 9 in tea.go, 1 in options.go, none in a _test.go file.
B returns 87: the same 80 plus these 7 in package tea's test files (1-based):
cursed_renderer_test.go:20:43 func (m *mouseRaceModel) Update(msg Msg) (Model, Cmd) {
exec_test.go:32:42 func (m *testExecModel) Update(msg Msg) (Model, Cmd) {
options_test.go:39:42 p := NewProgram(nil, WithFilter(func(_ Model, msg Msg) Msg { return msg }))
screen_test.go:37:42 func (m *testViewModel) Update(msg Msg) (Model, Cmd) {
tea_test.go:37:38 func (m *testModel) Update(msg Msg) (Model, Cmd) {
tea_test.go:244:20 p.filter = func(_ Model, msg Msg) Msg {
tea_test.go:623:38 func (m benchModel) Update(msg Msg) (Model, Cmd) {
These files have package tea, go list -test . lists them in TestGoFiles, none declares its own Model, and go vet . passes, so all 7 refer to tea.Model.
Opening and closing termios_unix.go without typing gives 80, and so does leaving it out. The edit has to be there, even though it is discarded. Waiting 60 s before the request does not change either answer. I repeated each variant 3 times with the same results.
What did you expect to see?
The same answer in both runs. Since the answer already includes the uses in tea.go and options.go, I expected the uses in the package's _test.go files as well.
#43144 (fixed in 2022, now locked) described find-references missing test files "unless you've opened the test's package". Here a file of the package (mouse.go) is open in both runs and opening termios_unix.go is not enough; only an edit to one of its files makes the test files appear. The request also comes from another module that reaches the package through a replace directive, which may matter. #73781 is about references across modules in general and does not cover this.
Editor and settings
No editor; a script over stdio (gopls serve). Settings returned for workspace/configuration: {"semanticTokens": true, "hints": {"assignVariableTypes": true, "compositeLiteralFields": true, "compositeLiteralTypes": true, "constantValues": true, "functionTypeParameters": true, "parameterNames": true, "rangeVariableTypes": true}}.
Logs
In the recorded run B below, after termios_unix.go is closed, gopls logs an extra go/packages.Load for the examples view whose query names charm.land/bubbletea/v2 and the example packages. In run A the examples view is only loaded once, with the pattern examples/....
LSP trace
URIs shortened to file:///repo/..., file texts elided; <ws> in log messages is the repository root. The recorded run B also opened examples/set-window-title/main.go early and closed and reopened it before closing termios_unix.go; that is not needed to reproduce, the steps above are enough.
Run B:
--> textDocument/didOpen {"textDocument":{"uri":"file:///repo/examples/http/main.go","languageId":"go","version":1,"text":"..."}}
<-- window/logMessage go/packages.Load #1 view_id="1" snapshot=0 query=[<ws>/... builtin] packages=4
--> textDocument/didOpen {"textDocument":{"uri":"file:///repo/examples/set-window-title/main.go","languageId":"go","version":1,"text":"..."}}
<-- window/logMessage go/packages.Load #2 view_id="2" snapshot=0 query=[<ws>/examples/... builtin] packages=66
--> textDocument/didOpen {"textDocument":{"uri":"file:///repo/termios_unix.go","languageId":"go","version":1,"text":"..."}}
--> textDocument/didChange {"textDocument":{"uri":"file:///repo/termios_unix.go","version":2},"contentChanges":[{"range":{"start":{"line":8,"character":1},"end":{"line":8,"character":1}},"text":"("}]}
--> textDocument/didClose {"textDocument":{"uri":"file:///repo/examples/set-window-title/main.go"}}
--> textDocument/didOpen {"textDocument":{"uri":"file:///repo/examples/set-window-title/main.go","languageId":"go","version":1,"text":"..."}}
--> textDocument/didClose {"textDocument":{"uri":"file:///repo/termios_unix.go"}}
--> textDocument/didOpen {"textDocument":{"uri":"file:///repo/mouse.go","languageId":"go","version":1,"text":"..."}}
<-- window/logMessage go/packages.Load #3 view_id="1" snapshot=8 query=[file=<ws>/mouse.go] packages=2
<-- window/logMessage go/packages.Load #4 view_id="2" snapshot=8 query=[charm.land/bubbles/v2/cursor ... charm.land/bubbletea/v2 examples/altscreen-toggle ... examples/split-editors ...]
--> textDocument/references {"textDocument":{"uri":"file:///repo/examples/http/main.go"},"position":{"line":37,"character":42},"context":{"includeDeclaration":true}}
<-- [87 locations, including
{"uri":"file:///repo/cursed_renderer_test.go","range":{"start":{"line":19,"character":42},"end":{"line":19,"character":47}}},
{"uri":"file:///repo/exec_test.go","range":{"start":{"line":31,"character":41},"end":{"line":31,"character":46}}},
{"uri":"file:///repo/options_test.go","range":{"start":{"line":38,"character":41},"end":{"line":38,"character":46}}},
{"uri":"file:///repo/screen_test.go","range":{"start":{"line":36,"character":41},"end":{"line":36,"character":46}}},
{"uri":"file:///repo/tea_test.go","range":{"start":{"line":36,"character":37},"end":{"line":36,"character":42}}},
{"uri":"file:///repo/tea_test.go","range":{"start":{"line":243,"character":19},"end":{"line":243,"character":24}}},
{"uri":"file:///repo/tea_test.go","range":{"start":{"line":622,"character":37},"end":{"line":622,"character":42}}}]
Run A (the same textDocument/references request):
<-- window/logMessage go/packages.Load #1 view_id="1" snapshot=0 query=[<ws>/... builtin] packages=4
<-- window/logMessage go/packages.Load #2 view_id="2" snapshot=0 query=[<ws>/examples/... builtin] packages=66
--> textDocument/references {"textDocument":{"uri":"file:///repo/examples/http/main.go"},"position":{"line":37,"character":42},"context":{"includeDeclaration":true}}
<-- [80 locations: 70 under examples/, tea.go lines 53, 60, 480, 489, 603, 751 (twice), 894, 999, options.go line 133; no _test.go file]
Activity
gabyhelp commentedon Sep 28, 2026
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
h9jiang commentedon Oct 1, 2026
+Needs Investigation
I think I previously investigated in #73781 so I will take another action item and reply later to this issue.