gopls/internal/lsp/cache: fix race in adhoc reloading
To avoid inconsistent state where we load command-line-arguments
packages for files that would be contained in an ad-hoc package, ensure
that the view is loaded before doing file loads, when in ad-hoc mode.
Along the way, introduce the concept of 'ViewType' discussed in our
zero-config-gopls design (golang/go#57979).
Furthermore, move certain data onto the immutable workspaceInformation
type:
- moduleMode depends only on ViewType
- inGOPATH can be precomputed
Updates golang/go#57979
Fixes golang/go#57209
Change-Id: If54cea65fbc72e6e704eccc6fe59d30ae5d01069
Reviewed-on: https://go-review.googlesource.com/c/tools/+/495256
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/cache/check.go b/gopls/internal/lsp/cache/check.go
index 83ea177..cf212c6 100644
--- a/gopls/internal/lsp/cache/check.go
+++ b/gopls/internal/lsp/cache/check.go
@@ -1190,7 +1190,7 @@
relatedInformation: s.view.Options().RelatedInformationSupported,
linkTarget: s.view.Options().LinkTarget,
- moduleMode: s.moduleMode(),
+ moduleMode: s.view.moduleMode(),
}, nil
}
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index 7988a72..64e1b55 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -327,47 +327,19 @@
if s.view.hasGopackagesDriver {
return true
}
+
// Check if the user is working within a module or if we have found
// multiple modules in the workspace.
if len(s.workspaceModFiles) > 0 {
return true
}
- // The user may have a multiple directories in their GOPATH.
- // Check if the workspace is within any of them.
+
// TODO(rfindley): this should probably be subject to "if GO111MODULES = off {...}".
- for _, gp := range filepath.SplitList(s.view.gopath) {
- if source.InDir(filepath.Join(gp, "src"), s.view.folder.Filename()) {
- return true
- }
- }
- return false
-}
-
-// moduleMode reports whether the current snapshot uses Go modules.
-//
-// From https://go.dev/ref/mod, module mode is active if either of the
-// following hold:
-// - GO111MODULE=on
-// - GO111MODULE=auto and we are inside a module or have a GOWORK value.
-//
-// Additionally, this method returns false if GOPACKAGESDRIVER is set.
-//
-// TODO(rfindley): use this more widely.
-func (s *snapshot) moduleMode() bool {
- // Since we only really understand the `go` command, if the user has a
- // different GOPACKAGESDRIVER, assume that their configuration is valid.
- if s.view.hasGopackagesDriver {
- return false
- }
-
- switch s.view.effectiveGO111MODULE() {
- case on:
+ if s.view.inGOPATH {
return true
- case off:
- return false
- default:
- return len(s.workspaceModFiles) > 0 || s.view.gowork != ""
}
+
+ return false
}
// workspaceMode describes the way in which the snapshot's workspace should
@@ -763,6 +735,18 @@
}
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
+ if s.view.ViewType() == AdHocView {
+ // As described in golang/go#57209, in ad-hoc workspaces (where we load ./
+ // rather than ./...), preempting the directory load with file loads can
+ // lead to an inconsistent outcome, where certain files are loaded with
+ // command-line-arguments packages and others are loaded only in the ad-hoc
+ // package. Therefore, ensure that the workspace is loaded before doing any
+ // file loads.
+ if err := s.awaitLoaded(ctx); err != nil {
+ return nil, err
+ }
+ }
+
s.mu.Lock()
// Start with the set of package associations derived from the last load.
diff --git a/gopls/internal/lsp/cache/view.go b/gopls/internal/lsp/cache/view.go
index d999170..1dc13aa 100644
--- a/gopls/internal/lsp/cache/view.go
+++ b/gopls/internal/lsp/cache/view.go
@@ -130,6 +130,10 @@
// GOPACKAGESDRIVER environment variable or a gopackagesdriver binary on
// their machine.
hasGopackagesDriver bool
+
+ // inGOPATH reports whether the workspace directory is contained in a GOPATH
+ // directory.
+ inGOPATH bool
}
// effectiveGO111MODULE reports the value of GO111MODULE effective in the go
@@ -145,6 +149,79 @@
}
}
+// A ViewType describes how we load package information for a view.
+//
+// This is used for constructing the go/packages.Load query, and for
+// interpreting missing packages, imports, or errors.
+//
+// Each view has a ViewType which is derived from its immutable workspace
+// information -- any environment change that would affect the view type
+// results in a new view.
+type ViewType int
+
+const (
+ // GoPackagesDriverView is a view with a non-empty GOPACKAGESDRIVER
+ // environment variable.
+ GoPackagesDriverView ViewType = iota
+
+ // GOPATHView is a view in GOPATH mode.
+ //
+ // I.e. in GOPATH, with GO111MODULE=off, or GO111MODULE=auto with no
+ // go.mod file.
+ GOPATHView
+
+ // GoModuleView is a view in module mode with a single Go module.
+ GoModuleView
+
+ // GoWorkView is a view in module mode with a go.work file.
+ GoWorkView
+
+ // An AdHocView is a collection of files in a given directory, not in GOPATH
+ // or a module.
+ AdHocView
+)
+
+// ViewType derives the type of the view from its workspace information.
+//
+// TODO(rfindley): this logic is overlapping and slightly inconsistent with
+// validBuildConfiguration. As part of zero-config-gopls (golang/go#57979), fix
+// this inconsistency and consolidate on the ViewType abstraction.
+func (w workspaceInformation) ViewType() ViewType {
+ if w.hasGopackagesDriver {
+ return GoPackagesDriverView
+ }
+ go111module := w.effectiveGO111MODULE()
+ if w.gowork != "" && go111module != off {
+ return GoWorkView
+ }
+ if w.gomod != "" && go111module != off {
+ return GoModuleView
+ }
+ if w.inGOPATH && go111module != on {
+ return GOPATHView
+ }
+ return AdHocView
+}
+
+// moduleMode reports whether the current snapshot uses Go modules.
+//
+// From https://go.dev/ref/mod, module mode is active if either of the
+// following hold:
+// - GO111MODULE=on
+// - GO111MODULE=auto and we are inside a module or have a GOWORK value.
+//
+// Additionally, this method returns false if GOPACKAGESDRIVER is set.
+//
+// TODO(rfindley): use this more widely.
+func (w workspaceInformation) moduleMode() bool {
+ switch w.ViewType() {
+ case GoModuleView, GoWorkView:
+ return true
+ default:
+ return false
+ }
+}
+
// GOWORK returns the effective GOWORK value for this workspace, if
// any, in URI form.
//
@@ -740,6 +817,8 @@
})
}
+ // TODO(rfindley): this should be predicated on the s.view.moduleMode().
+ // There is no point loading ./... if we have an empty go.work.
if len(s.workspaceModFiles) > 0 {
for modURI := range s.workspaceModFiles {
// Verify that the modfile is valid before trying to load it.
@@ -881,7 +960,7 @@
if err != nil {
return info, err
}
- if err := info.goEnv.load(ctx, folder.Filename(), options.EnvSlice(), s.gocmdRunner); err != nil {
+ if err := info.load(ctx, folder.Filename(), options.EnvSlice(), s.gocmdRunner); err != nil {
return info, err
}
// The value of GOPACKAGESDRIVER is not returned through the go command.
@@ -899,6 +978,13 @@
return info, err
}
+ // Check if the workspace is within any GOPATH directory.
+ for _, gp := range filepath.SplitList(info.gopath) {
+ if source.InDir(filepath.Join(gp, "src"), folder.Filename()) {
+ info.inGOPATH = true
+ break
+ }
+ }
return info, nil
}