internal/lsp: fix a few staticcheck suggestions, some cleanup

There were a few merge conflict-related issues in the GC optimization
details CL. Also fixed a few things I noticed after the fact, like
separating out a new mutex.

Staticcheck caught a few things, and I also fixed a bug I noticed
in the cache package.

Change-Id: I3fc519373253418586dca08fdec3114b30a247ea
Reviewed-on: https://go-review.googlesource.com/c/tools/+/245399
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index 9476efa..099457f 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -351,6 +351,9 @@
 			dep, version := info[0], info[2]
 
 			// Make sure that the format matches our expectation.
+			if len(version) < 2 {
+				continue
+			}
 			if version[0] != '[' || version[len(version)-1] != ']' {
 				continue
 			}
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
index ebe440e..66321fb 100644
--- a/internal/lsp/cache/pkg.go
+++ b/internal/lsp/cache/pkg.go
@@ -40,9 +40,11 @@
 )
 
 // Declare explicit types for files and directories to distinguish between the two.
-type fileURI span.URI
-type directoryURI span.URI
-type viewLoadScope span.URI
+type (
+	fileURI       span.URI
+	directoryURI  span.URI
+	viewLoadScope span.URI
+)
 
 func (p *pkg) ID() string {
 	return string(p.m.id)
@@ -114,9 +116,9 @@
 	return nil, errors.Errorf("no imported package for %s", pkgPath)
 }
 
-func (pkg *pkg) MissingDependencies() []string {
+func (p *pkg) MissingDependencies() []string {
 	var md []string
-	for i := range pkg.m.missingDeps {
+	for i := range p.m.missingDeps {
 		md = append(md, string(i))
 	}
 	return md
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 5632398..2b51dd7 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -145,23 +145,21 @@
 		}
 		err := s.directGoModCommand(ctx, uri, "get", goCmdArgs...)
 		return nil, err
-	default:
-		return nil, fmt.Errorf("unknown command: %s", params.Command)
 	case source.CommandToggleDetails:
 		var fileURI span.URI
 		if err := source.UnmarshalArgs(params.Arguments, &fileURI); err != nil {
 			return nil, err
 		}
 		pkgDir := span.URI(path.Dir(fileURI.Filename()))
-		s.deliveredMu.Lock()
-		if s.gcOptimizatonDetails[pkgDir] {
+		s.gcOptimizationDetailsMu.Lock()
+		if _, ok := s.gcOptimizatonDetails[pkgDir]; ok {
 			delete(s.gcOptimizatonDetails, pkgDir)
 		} else {
-			s.gcOptimizatonDetails[pkgDir] = true
+			s.gcOptimizatonDetails[pkgDir] = struct{}{}
 		}
+		s.gcOptimizationDetailsMu.Unlock()
 		event.Log(ctx, fmt.Sprintf("gc_details %s now %v %v", pkgDir, s.gcOptimizatonDetails[pkgDir],
 			s.gcOptimizatonDetails))
-		s.deliveredMu.Unlock()
 		// need to recompute diagnostics.
 		// so find the snapshot
 		sv, err := s.session.ViewOf(fileURI)
@@ -170,6 +168,8 @@
 		}
 		s.diagnoseSnapshot(sv.Snapshot())
 		return nil, nil
+	default:
+		return nil, fmt.Errorf("unknown command: %s", params.Command)
 	}
 	return nil, nil
 }
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 873413b..ef7aec6 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -117,32 +117,24 @@
 		go func(pkg source.Package) {
 			defer wg.Done()
 
-			detailsDir := ""
-			// Only run analyses for packages with open files.
-			withAnalysis := alwaysAnalyze
+			withAnalysis := alwaysAnalyze // only run analyses for packages with open files
+			var gcDetailsDir span.URI     // find the package's optimization details, if available
 			for _, pgf := range pkg.CompiledGoFiles() {
 				if snapshot.IsOpen(pgf.URI) {
 					withAnalysis = true
 				}
-				if detailsDir == "" {
-					dir := filepath.Dir(pgf.URI.Filename())
-					if s.gcOptimizatonDetails[span.URI(dir)] {
-						detailsDir = dir
+				if gcDetailsDir == "" {
+					dirURI := span.URIFromPath(filepath.Dir(pgf.URI.Filename()))
+					s.gcOptimizationDetailsMu.Lock()
+					_, ok := s.gcOptimizatonDetails[dirURI]
+					s.gcOptimizationDetailsMu.Unlock()
+					if ok {
+						gcDetailsDir = dirURI
 					}
 				}
 			}
 
 			pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, withAnalysis)
-			if detailsDir != "" {
-				var more map[source.FileIdentity][]*source.Diagnostic
-				more, err = source.DoGcDetails(ctx, snapshot, detailsDir)
-				if err != nil {
-					event.Error(ctx, "warning: gcdetails", err, tag.Snapshot.Of(snapshot.ID()))
-				}
-				for k, v := range more {
-					pkgReports[k] = append(pkgReports[k], v...)
-				}
-			}
 
 			// Check if might want to warn the user about their build configuration.
 			// Our caller decides whether to send the message.
@@ -171,6 +163,26 @@
 					reports[key][diagnosticKey(d)] = d
 				}
 			}
+			// If gc optimization details are available, add them to the
+			// diagnostic reports.
+			if gcDetailsDir != "" {
+				gcReports, err := source.GCOptimizationDetails(ctx, snapshot, gcDetailsDir)
+				if err != nil {
+					event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()))
+				}
+				for id, diags := range gcReports {
+					key := idWithAnalysis{
+						id:           id,
+						withAnalysis: withAnalysis,
+					}
+					if _, ok := reports[key]; !ok {
+						reports[key] = map[string]*source.Diagnostic{}
+					}
+					for _, d := range diags {
+						reports[key][diagnosticKey(d)] = d
+					}
+				}
+			}
 			reportsMu.Unlock()
 		}(pkg)
 	}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index a362c03..5fbb757 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -24,7 +24,7 @@
 func NewServer(session source.Session, client protocol.Client) *Server {
 	return &Server{
 		delivered:            make(map[span.URI]sentDiagnostics),
-		gcOptimizatonDetails: make(map[span.URI]bool),
+		gcOptimizatonDetails: make(map[span.URI]struct{}),
 		session:              session,
 		client:               client,
 		diagnosticsSema:      make(chan struct{}, concurrentAnalyses),
@@ -74,9 +74,11 @@
 	deliveredMu sync.Mutex
 	delivered   map[span.URI]sentDiagnostics
 
-	// gcOptimizationDetails describes which packages we want optimization details
-	// included in the diagnostics. The key is the directory of the package.
-	gcOptimizatonDetails map[span.URI]bool
+	// gcOptimizationDetails describes the packages for which we want
+	// optimization details to be included in the diagnostics. The key is the
+	// directory of the package.
+	gcOptimizationDetailsMu sync.Mutex
+	gcOptimizatonDetails    map[span.URI]struct{}
 
 	// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
 	diagnosticsSema chan struct{}
diff --git a/internal/lsp/source/code_lens.go b/internal/lsp/source/code_lens.go
index b0f146f..5a633ee 100644
--- a/internal/lsp/source/code_lens.go
+++ b/internal/lsp/source/code_lens.go
@@ -225,6 +225,9 @@
 
 func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
 	_, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage)
+	if err != nil {
+		return nil, err
+	}
 	fset := snapshot.View().Session().Cache().FileSet()
 	rng, err := newMappedRange(fset, pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
 	if err != nil {
diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go
index 4d6e028..3dd8be8 100644
--- a/internal/lsp/source/gc_annotations.go
+++ b/internal/lsp/source/gc_annotations.go
@@ -18,12 +18,12 @@
 	"golang.org/x/tools/internal/span"
 )
 
-func DoGcDetails(ctx context.Context, snapshot Snapshot, pkgDir string) (map[FileIdentity][]*Diagnostic, error) {
+func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[FileIdentity][]*Diagnostic, error) {
 	outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
 	if err := os.MkdirAll(outDir, 0700); err != nil {
 		return nil, err
 	}
-	args := []string{fmt.Sprintf("-gcflags=-json=0,%s", outDir), pkgDir}
+	args := []string{fmt.Sprintf("-gcflags=-json=0,%s", outDir), pkgDir.Filename()}
 	err := snapshot.RunGoCommandDirect(ctx, "build", args)
 	if err != nil {
 		return nil, err