gopls/internal/lsp: keep track of overlays on the files map

The overlays method showed up as a hot spot in the google-cloud-go repo
(via reloadOrphanedFiles), because it walks all files to find the small
number of overlays.

We already have a filesMap abstraction; use it to keep track of overlays
in a separate map.

For golang/go#60089

Change-Id: I62c6c688d012beaa4b0f255225993da961cb9dad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/496442
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/cache/maps.go b/gopls/internal/lsp/cache/maps.go
index 0ad4ac9..533c339 100644
--- a/gopls/internal/lsp/cache/maps.go
+++ b/gopls/internal/lsp/cache/maps.go
@@ -15,7 +15,8 @@
 // TODO(euroelessar): Use generics once support for go1.17 is dropped.
 
 type filesMap struct {
-	impl *persistent.Map
+	impl       *persistent.Map
+	overlayMap map[span.URI]*Overlay // the subset that are overlays
 }
 
 // uriLessInterface is the < relation for "any" values containing span.URIs.
@@ -25,13 +26,19 @@
 
 func newFilesMap() filesMap {
 	return filesMap{
-		impl: persistent.NewMap(uriLessInterface),
+		impl:       persistent.NewMap(uriLessInterface),
+		overlayMap: make(map[span.URI]*Overlay),
 	}
 }
 
 func (m filesMap) Clone() filesMap {
+	overlays := make(map[span.URI]*Overlay, len(m.overlayMap))
+	for k, v := range m.overlayMap {
+		overlays[k] = v
+	}
 	return filesMap{
-		impl: m.impl.Clone(),
+		impl:       m.impl.Clone(),
+		overlayMap: overlays,
 	}
 }
 
@@ -55,10 +62,30 @@
 
 func (m filesMap) Set(key span.URI, value source.FileHandle) {
 	m.impl.Set(key, value, nil)
+
+	if o, ok := value.(*Overlay); ok {
+		m.overlayMap[key] = o
+	} else {
+		// Setting a non-overlay must delete the corresponding overlay, to preserve
+		// the accuracy of the overlay set.
+		delete(m.overlayMap, key)
+	}
 }
 
-func (m filesMap) Delete(key span.URI) {
+func (m *filesMap) Delete(key span.URI) {
 	m.impl.Delete(key)
+	delete(m.overlayMap, key)
+}
+
+// overlays returns a new unordered array of overlay files.
+func (m filesMap) overlays() []*Overlay {
+	// In practice we will always have at least one overlay, so there is no need
+	// to optimize for the len=0 case by returning a nil slice.
+	overlays := make([]*Overlay, 0, len(m.overlayMap))
+	for _, o := range m.overlayMap {
+		overlays = append(overlays, o)
+	}
+	return overlays
 }
 
 func packageIDLessInterface(x, y interface{}) bool {
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index 7567589..bf22164 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -624,21 +624,11 @@
 	return overlays
 }
 
-// TODO(rfindley): investigate whether it would be worthwhile to keep track of
-// overlays when we get them via GetFile.
 func (s *snapshot) overlays() []*Overlay {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	var overlays []*Overlay
-	s.files.Range(func(uri span.URI, fh source.FileHandle) {
-		overlay, ok := fh.(*Overlay)
-		if !ok {
-			return
-		}
-		overlays = append(overlays, overlay)
-	})
-	return overlays
+	return s.files.overlays()
 }
 
 // Package data kinds, identifying various package data that may be stored in