Skip to content

Commit 01c9ff0

Browse files
committed
internal/lsp/cache: invalid packages should not be workspace packages
To support the experimentalUseInvalidMetadata mode, we keep around invalid metadata. This can help gopls features work when, for example, the go.mod file is broken. It is debatable whether this feature is worth supporting (see golang/go#54180), but in the meantime there is a very negative side-effect when module paths are changed in the go.mod file: we keep around a bunch of workspace packages with a stale module path. As a result we can be left with a lots of extra type-checked packages in memory, and many inaccurate diagnostics. Fix this by skipping packages with invalid metadata when computing workspace packages. While we may want to use invalid metadata when finding the best package for a file, it does not make sense to re- type-check and diagnose all those stale packages. Fixes golang/go#43186 Change-Id: Id73b47ea138ec80a9de63b03dae41d4e509b8d5a Reviewed-on: https://go-review.googlesource.com/c/tools/+/420956 Run-TryBot: Robert Findley <[email protected]> Reviewed-by: Suzy Mueller <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent bd68922 commit 01c9ff0

File tree

5 files changed

+78
-6
lines changed

5 files changed

+78
-6
lines changed

gopls/internal/regtest/workspace/broken_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,55 @@ const CompleteMe = 222
115115
}
116116
})
117117
}
118+
119+
// Test for golang/go#43186: correcting the module path should fix errors
120+
// without restarting gopls.
121+
func TestBrokenWorkspace_WrongModulePath(t *testing.T) {
122+
const files = `
123+
-- go.mod --
124+
module mod.testx
125+
126+
go 1.18
127+
-- p/internal/foo/foo.go --
128+
package foo
129+
130+
const C = 1
131+
-- p/internal/bar/bar.go --
132+
package bar
133+
134+
import "mod.test/p/internal/foo"
135+
136+
const D = foo.C + 1
137+
-- p/internal/bar/bar_test.go --
138+
package bar_test
139+
140+
import (
141+
"mod.test/p/internal/foo"
142+
. "mod.test/p/internal/bar"
143+
)
144+
145+
const E = D + foo.C
146+
-- p/internal/baz/baz_test.go --
147+
package baz_test
148+
149+
import (
150+
named "mod.test/p/internal/bar"
151+
)
152+
153+
const F = named.D - 3
154+
`
155+
156+
Run(t, files, func(t *testing.T, env *Env) {
157+
env.OpenFile("p/internal/bar/bar.go")
158+
env.Await(
159+
OnceMet(
160+
env.DoneWithOpen(),
161+
env.DiagnosticAtRegexp("p/internal/bar/bar.go", "\"mod.test/p/internal/foo\""),
162+
),
163+
)
164+
env.OpenFile("go.mod")
165+
env.RegexpReplace("go.mod", "mod.testx", "mod.test")
166+
env.SaveBuffer("go.mod") // saving triggers a reload
167+
env.Await(NoOutstandingDiagnostics())
168+
})
169+
}

internal/lsp/cache/check.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
355355
missing, unexpected = filter.ProcessErrors(pkg.typeErrors)
356356
}
357357
if len(unexpected) != 0 || len(missing) != 0 {
358+
// TODO(rfindley): remove this distracting log
358359
event.Log(ctx, fmt.Sprintf("falling back to safe trimming due to type errors: %v or still-missing identifiers: %v", unexpected, missing), tag.Package.Of(string(m.ID)))
359360
pkg, err = doTypeCheck(ctx, snapshot, goFiles, compiledGoFiles, m, mode, deps, nil)
360361
if err != nil {

internal/lsp/cache/load.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,12 @@ func containsFileInWorkspaceLocked(s *snapshot, m *Metadata) bool {
635635
func computeWorkspacePackagesLocked(s *snapshot, meta *metadataGraph) map[PackageID]PackagePath {
636636
workspacePackages := make(map[PackageID]PackagePath)
637637
for _, m := range meta.metadata {
638+
// Don't consider invalid packages to be workspace packages. Doing so can
639+
// result in type-checking and diagnosing packages that no longer exist,
640+
// which can lead to memory leaks and confusing errors.
641+
if !m.Valid {
642+
continue
643+
}
638644
if !containsPackageLocked(s, m.Metadata) {
639645
continue
640646
}

internal/lsp/cache/snapshot.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,13 +1874,8 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
18741874
result.actions.Delete(key)
18751875
}
18761876

1877-
// If the workspace mode has changed, we must delete all metadata, as it
1878-
// is unusable and may produce confusing or incorrect diagnostics.
18791877
// If a file has been deleted, we must delete metadata for all packages
18801878
// containing that file.
1881-
workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
1882-
1883-
// Don't keep package metadata for packages that have lost files.
18841879
//
18851880
// TODO(rfindley): why not keep invalid metadata in this case? If we
18861881
// otherwise allow operate on invalid metadata, why not continue to do so,
@@ -1907,11 +1902,27 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
19071902
result.shouldLoad[k] = v
19081903
}
19091904

1905+
// TODO(rfindley): consolidate the this workspace mode detection with
1906+
// workspace invalidation.
1907+
workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
1908+
1909+
// We delete invalid metadata in the following cases:
1910+
// - If we are forcing a reload of metadata.
1911+
// - If the workspace mode has changed, as stale metadata may produce
1912+
// confusing or incorrect diagnostics.
1913+
//
1914+
// TODO(rfindley): we should probably also clear metadata if we are
1915+
// reinitializing the workspace, as otherwise we could leave around a bunch
1916+
// of irrelevant and duplicate metadata (for example, if the module path
1917+
// changed). However, this breaks the "experimentalUseInvalidMetadata"
1918+
// feature, which relies on stale metadata when, for example, a go.mod file
1919+
// is broken via invalid syntax.
1920+
deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
1921+
19101922
// Compute which metadata updates are required. We only need to invalidate
19111923
// packages directly containing the affected file, and only if it changed in
19121924
// a relevant way.
19131925
metadataUpdates := make(map[PackageID]*KnownMetadata)
1914-
deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
19151926
for k, v := range s.meta.metadata {
19161927
invalidateMetadata := idsToInvalidate[k]
19171928

internal/lsp/source/util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ func IsValidImport(pkgPath, importPkgPath string) bool {
555555
if IsCommandLineArguments(string(pkgPath)) {
556556
return true
557557
}
558+
// TODO(rfindley): this is wrong. mod.testx/p should not be able to
559+
// import mod.test/internal: https://go.dev/play/p/-Ca6P-E4V4q
558560
return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
559561
}
560562

0 commit comments

Comments
 (0)