gopls/internal/lsp/source: remove 2nd last call to CachedImportPaths

The Snapshot.CachedImportPaths method is called twice, both from
completions. This change eliminates one use by using Metadata instead;
and reduces the method (and renames it to CachedPackages) by
moving most of the logic into the last caller, which will be
removed in a (much more difficult) follow-up.

Change-Id: I58e14c2be9f77a0a39ab553d42eb790f77af5b7d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/471637
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index d0885f0..f93247a 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -1092,7 +1092,7 @@
 	return meta, nil
 }
 
-func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]*types.Package, error) {
+func (s *snapshot) CachedPackages(ctx context.Context) []source.Package {
 	// Don't reload workspace package metadata.
 	// This function is meant to only return currently cached information.
 	s.AwaitInitialized(ctx)
@@ -1100,42 +1100,15 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	pkgs := make(map[PackagePath]*syntaxPackage)
-
-	// Find all cached packages that are imported a nonzero amount of time.
-	//
-	// TODO(rfindley): this is pre-existing behavior, and a test fails if we
-	// don't do the importCount filter, but why do we care if a package is
-	// imported a nonzero amount of times?
-	imported := make(map[PackagePath]bool)
+	var pkgs []source.Package
 	s.packages.Range(func(_, v interface{}) {
 		ph := v.(*packageHandle)
-		for dep := range ph.m.DepsByPkgPath {
-			imported[dep] = true
-		}
-		if ph.m.Name == "main" {
-			return
-		}
 		pkg, err := ph.cached()
-		if err != nil {
-			return
-		}
-		if old, ok := pkgs[ph.m.PkgPath]; ok {
-			if len(pkg.compiledGoFiles) < len(old.compiledGoFiles) {
-				pkgs[ph.m.PkgPath] = pkg
-			}
-		} else {
-			pkgs[ph.m.PkgPath] = pkg
+		if err == nil {
+			pkgs = append(pkgs, &Package{ph.m, pkg})
 		}
 	})
-	results := make(map[PackagePath]*types.Package)
-	for pkgPath, pkg := range pkgs {
-		if imported[pkgPath] {
-			results[pkgPath] = pkg.types
-		}
-	}
-
-	return results, nil
+	return pkgs
 }
 
 // TODO(rfindley): clarify that this is only active modules. Or update to just
diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go
index b78699f..e43ebc9 100644
--- a/gopls/internal/lsp/source/completion/completion.go
+++ b/gopls/internal/lsp/source/completion/completion.go
@@ -359,6 +359,7 @@
 // candidate represents a completion candidate.
 type candidate struct {
 	// obj is the types.Object to complete to.
+	// TODO(adonovan): eliminate dependence on go/types throughout this struct.
 	obj types.Object
 
 	// score is used to rank candidates.
@@ -1137,9 +1138,37 @@
 
 func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error {
 	// Try loaded packages first. They're relevant, fast, and fully typed.
-	known, err := c.snapshot.CachedImportPaths(ctx)
-	if err != nil {
-		return err
+	//
+	// Find all cached packages that are imported a nonzero amount of time.
+	//
+	// TODO(rfindley): this is pre-existing behavior, and a test fails if we
+	// don't do the importCount filter, but why do we care if a package is
+	// imported a nonzero amount of times?
+	//
+	// TODO(adonovan): avoid the need for type-checked packges entirely here.
+	pkgs := make(map[source.PackagePath]source.Package)
+	imported := make(map[source.PackagePath]bool)
+	for _, pkg := range c.snapshot.CachedPackages(ctx) {
+		m := pkg.Metadata()
+		for dep := range m.DepsByPkgPath {
+			imported[dep] = true
+		}
+		if m.Name == "main" {
+			continue
+		}
+		if old, ok := pkgs[m.PkgPath]; ok {
+			if len(m.CompiledGoFiles) < len(old.Metadata().CompiledGoFiles) {
+				pkgs[m.PkgPath] = pkg
+			}
+		} else {
+			pkgs[m.PkgPath] = pkg
+		}
+	}
+	known := make(map[source.PackagePath]*types.Package)
+	for pkgPath, pkg := range pkgs {
+		if imported[pkgPath] {
+			known[pkgPath] = pkg.GetTypes()
+		}
 	}
 
 	var paths []string
@@ -1481,21 +1510,28 @@
 
 	count := 0
 
-	// TODO(adonovan): strength-reduce to a metadata query.
-	// All that's needed below is Package.{Name,Path}.
-	// Presumably that can be answered more thoroughly more quickly.
-	known, err := c.snapshot.CachedImportPaths(ctx)
+	// Search packages across the entire workspace.
+	all, err := c.snapshot.AllMetadata(ctx)
 	if err != nil {
 		return err
 	}
+	pkgNameByPath := make(map[source.PackagePath]string)
 	var paths []string // actually PackagePaths
-	for path, pkg := range known {
-		if !strings.HasPrefix(pkg.Name(), prefix) {
-			continue
+	for _, m := range all {
+		if m.ForTest != "" {
+			continue // skip all test variants
 		}
-		paths = append(paths, string(path))
+		if m.Name == "main" {
+			continue // main is non-importable
+		}
+		if !strings.HasPrefix(string(m.Name), prefix) {
+			continue // not a match
+		}
+		paths = append(paths, string(m.PkgPath))
+		pkgNameByPath[m.PkgPath] = string(m.Name)
 	}
 
+	// Rank candidates using goimports' algorithm.
 	var relevances map[string]float64
 	if len(paths) != 0 {
 		if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
@@ -1506,7 +1542,6 @@
 			return err
 		}
 	}
-
 	sort.Slice(paths, func(i, j int) bool {
 		if relevances[paths[i]] != relevances[paths[j]] {
 			return relevances[paths[i]] > relevances[paths[j]]
@@ -1518,22 +1553,22 @@
 	})
 
 	for _, path := range paths {
-		pkg := known[source.PackagePath(path)]
-		if _, ok := seen[pkg.Name()]; ok {
+		name := pkgNameByPath[source.PackagePath(path)]
+		if _, ok := seen[name]; ok {
 			continue
 		}
 		imp := &importInfo{
 			importPath: path,
 		}
-		if imports.ImportPathToAssumedName(path) != pkg.Name() {
-			imp.name = pkg.Name()
+		if imports.ImportPathToAssumedName(path) != name {
+			imp.name = name
 		}
 		if count >= maxUnimportedPackageNames {
 			return nil
 		}
 		c.deepState.enqueue(candidate{
 			// Pass an empty *types.Package to disable deep completions.
-			obj:   types.NewPkgName(0, nil, pkg.Name(), types.NewPackage(path, string(pkg.Name()))),
+			obj:   types.NewPkgName(0, nil, name, types.NewPackage(path, name)),
 			score: unimportedScore(relevances[path]),
 			imp:   imp,
 		})
diff --git a/gopls/internal/lsp/source/view.go b/gopls/internal/lsp/source/view.go
index 1bfca02..7623b60 100644
--- a/gopls/internal/lsp/source/view.go
+++ b/gopls/internal/lsp/source/view.go
@@ -156,13 +156,13 @@
 	// excluding id itself.
 	ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*Metadata, error)
 
-	// CachedImportPaths returns all the imported packages loaded in this
-	// snapshot, indexed by their package path (not import path, despite the name)
-	// and checked in TypecheckWorkspace mode.
+	// CachedPackages returns a new, unordered array of all
+	// packages currently cached in this snapshot, which is a
+	// poorly defined set that depends on the history of
+	// operations up to this point. Do not use it.
 	//
-	// To reduce latency, it does not wait for type-checking to complete.
-	// It is intended for use only in completions.
-	CachedImportPaths(ctx context.Context) (map[PackagePath]*types.Package, error)
+	// TODO(adonovan): get rid of the last call from completions.
+	CachedPackages(ctx context.Context) []Package
 
 	// ActiveMetadata returns a new, unordered slice containing
 	// metadata for all packages considered 'active' in the workspace.