gopls/internal/lsp/source/xrefs: allow Lookup of a set

This change generalizes xrefs.Lookup to allow lookup of
a set of (package path, object path) targets in a single
call, amortizing decoding. It will be used to support
exported methods in implementsV2.

Change-Id: If10c21db97df7a9e8088b3a98e81018d12c52fb2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/463140
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cache/pkg.go b/gopls/internal/lsp/cache/pkg.go
index 0a986de..37aae50 100644
--- a/gopls/internal/lsp/cache/pkg.go
+++ b/gopls/internal/lsp/cache/pkg.go
@@ -178,13 +178,15 @@
 	return res
 }
 
-func (p *pkg) ReferencesTo(pkgPath PackagePath, objPath objectpath.Path) []protocol.Location {
+// ReferencesTo returns the location of each reference within package p
+// to one of the target objects denoted by the pair (package path, object path).
+func (p *pkg) ReferencesTo(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location {
 	// TODO(adonovan): In future, p.xrefs will be retrieved from a
 	// section of the cache file produced by type checking.
 	// (Other sections will include the package's export data,
 	// "implements" relations, exported symbols, etc.)
 	// For now we just hang it off the pkg.
-	return xrefs.Lookup(p.m, p.xrefs, pkgPath, objPath)
+	return xrefs.Lookup(p.m, p.xrefs, targets)
 }
 
 func (p *pkg) MethodSetsIndex() *methodsets.Index {
diff --git a/gopls/internal/lsp/source/references2.go b/gopls/internal/lsp/source/references2.go
index 1fc01dc..538639a 100644
--- a/gopls/internal/lsp/source/references2.go
+++ b/gopls/internal/lsp/source/references2.go
@@ -290,9 +290,9 @@
 
 	// Is the object exported?
 	// (objectpath succeeds for lowercase names, arguably a bug.)
-	var exportedObjectPath objectpath.Path
+	var exportedObjectPaths map[objectpath.Path]unit
 	if path, err := objectpath.For(obj); err == nil && obj.Exported() {
-		exportedObjectPath = path
+		exportedObjectPaths = map[objectpath.Path]unit{path: unit{}}
 
 		// If the object is an exported method, we need to search for
 		// all matching implementations (using the incremental
@@ -342,10 +342,12 @@
 			return localReferences(ctx, snapshot, declURI, declPosn.Offset, m, report)
 		})
 
-		if exportedObjectPath == "" {
+		if exportedObjectPaths == nil {
 			continue // non-exported
 		}
 
+		targets := map[PackagePath]map[objectpath.Path]struct{}{m.PkgPath: exportedObjectPaths}
+
 		// global
 		group.Go(func() error {
 			// Compute the global-scope query for every variant
@@ -358,7 +360,7 @@
 			for _, rdep := range rdeps {
 				rdep := rdep
 				group.Go(func() error {
-					return globalReferences(ctx, snapshot, rdep, m.PkgPath, exportedObjectPath, report)
+					return globalReferences(ctx, snapshot, rdep, targets, report)
 				})
 			}
 			return nil
@@ -504,9 +506,9 @@
 	return targets, nil
 }
 
-// globalReferences reports each cross-package
-// reference to the object identified by (pkgPath, objectPath).
-func globalReferences(ctx context.Context, snapshot Snapshot, m *Metadata, pkgPath PackagePath, objectPath objectpath.Path, report func(loc protocol.Location, isDecl bool)) error {
+// globalReferences reports each cross-package reference to one of the
+// target objects denoted by (package path, object path).
+func globalReferences(ctx context.Context, snapshot Snapshot, m *Metadata, targets map[PackagePath]map[objectpath.Path]unit, report func(loc protocol.Location, isDecl bool)) error {
 	// TODO(adonovan): opt: don't actually type-check here,
 	// since we quite intentionally don't look at type information.
 	// Instead, access the reference index computed during
@@ -515,7 +517,7 @@
 	if err != nil {
 		return err
 	}
-	for _, loc := range pkgs[0].ReferencesTo(pkgPath, objectPath) {
+	for _, loc := range pkgs[0].ReferencesTo(targets) {
 		report(loc, false)
 	}
 	return nil
diff --git a/gopls/internal/lsp/source/view.go b/gopls/internal/lsp/source/view.go
index 640a32e..81a91cc 100644
--- a/gopls/internal/lsp/source/view.go
+++ b/gopls/internal/lsp/source/view.go
@@ -773,11 +773,13 @@
 	ResolveImportPath(path ImportPath) (Package, error)
 	Imports() []Package // new slice of all direct dependencies, unordered
 	HasTypeErrors() bool
-	DiagnosticsForFile(uri span.URI) []*Diagnostic                 // new array of list/parse/type errors
-	ReferencesTo(PackagePath, objectpath.Path) []protocol.Location // new sorted array of xrefs
+	DiagnosticsForFile(uri span.URI) []*Diagnostic                             // new array of list/parse/type errors
+	ReferencesTo(map[PackagePath]map[objectpath.Path]unit) []protocol.Location // new sorted array of xrefs
 	MethodSetsIndex() *methodsets.Index
 }
 
+type unit = struct{}
+
 // A CriticalError is a workspace-wide error that generally prevents gopls from
 // functioning correctly. In the presence of critical errors, other diagnostics
 // in the workspace may not make sense.
diff --git a/gopls/internal/lsp/source/xrefs/xrefs.go b/gopls/internal/lsp/source/xrefs/xrefs.go
index db9bab1..b0d2207 100644
--- a/gopls/internal/lsp/source/xrefs/xrefs.go
+++ b/gopls/internal/lsp/source/xrefs/xrefs.go
@@ -134,8 +134,9 @@
 
 // Lookup searches a serialized index produced by an indexPackage
 // operation on m, and returns the locations of all references from m
-// to the object denoted by (pkgPath, objectPath).
-func Lookup(m *source.Metadata, data []byte, pkgPath source.PackagePath, objPath objectpath.Path) []protocol.Location {
+// to any object in the target set. Each object is denoted by a pair
+// of (package path, object path).
+func Lookup(m *source.Metadata, data []byte, targets map[source.PackagePath]map[objectpath.Path]struct{}) (locs []protocol.Location) {
 
 	// TODO(adonovan): opt: evaluate whether it would be faster to decode
 	// in two passes, first with struct { PkgPath string; Objects BLOB }
@@ -146,10 +147,9 @@
 	mustDecode(data, &packages)
 
 	for _, gp := range packages {
-		if gp.PkgPath == pkgPath {
-			var locs []protocol.Location
+		if objectSet, ok := targets[gp.PkgPath]; ok {
 			for _, gobObj := range gp.Objects {
-				if gobObj.Path == objPath {
+				if _, ok := objectSet[gobObj.Path]; ok {
 					for _, ref := range gobObj.Refs {
 						uri := m.CompiledGoFiles[ref.FileIndex]
 						locs = append(locs, protocol.Location{
@@ -159,10 +159,10 @@
 					}
 				}
 			}
-			return locs
 		}
 	}
-	return nil // this package does not reference that one
+
+	return locs
 }
 
 // -- serialized representation --