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/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.