aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/cache
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/cache')
-rw-r--r--gopls/internal/lsp/cache/analysis.go11
-rw-r--r--gopls/internal/lsp/cache/cache.go2
-rw-r--r--gopls/internal/lsp/cache/check.go293
-rw-r--r--gopls/internal/lsp/cache/errors.go74
-rw-r--r--gopls/internal/lsp/cache/errors_test.go (renamed from gopls/internal/lsp/cache/error_test.go)0
-rw-r--r--gopls/internal/lsp/cache/load.go14
-rw-r--r--gopls/internal/lsp/cache/pkg.go205
-rw-r--r--gopls/internal/lsp/cache/snapshot.go88
8 files changed, 380 insertions, 307 deletions
diff --git a/gopls/internal/lsp/cache/analysis.go b/gopls/internal/lsp/cache/analysis.go
index e9f1c7f7c..0005b0e44 100644
--- a/gopls/internal/lsp/cache/analysis.go
+++ b/gopls/internal/lsp/cache/analysis.go
@@ -520,10 +520,8 @@ func analysisCacheKey(analyzers []*analysis.Analyzer, m *source.Metadata, compil
sz := m.TypesSizes.(*types.StdSizes)
fmt.Fprintf(hasher, "sizes: %d %d\n", sz.WordSize, sz.MaxAlign)
- // metadata errors
- for _, err := range m.Errors {
- fmt.Fprintf(hasher, "error: %q", err)
- }
+ // metadata errors: used for 'compiles' field
+ fmt.Fprintf(hasher, "errors: %d", len(m.Errors))
// module Go version
if m.Module != nil && m.Module.GoVersion != "" {
@@ -542,8 +540,8 @@ func analysisCacheKey(analyzers []*analysis.Analyzer, m *source.Metadata, compil
depIDs = append(depIDs, string(depID))
}
sort.Strings(depIDs)
- for _, id := range depIDs {
- vdep := vdeps[PackageID(id)]
+ for _, depID := range depIDs {
+ vdep := vdeps[PackageID(depID)]
fmt.Fprintf(hasher, "dep: %s\n", vdep.PkgPath)
fmt.Fprintf(hasher, "export: %s\n", vdep.DeepExportHash)
@@ -766,6 +764,7 @@ func typeCheckForAnalysis(fset *token.FileSet, parsed []*source.ParsedGoFile, m
}
cfg := &types.Config{
+ Sizes: m.TypesSizes,
Error: func(e error) {
pkg.compiles = false // type error
pkg.typeErrors = append(pkg.typeErrors, e.(types.Error))
diff --git a/gopls/internal/lsp/cache/cache.go b/gopls/internal/lsp/cache/cache.go
index ef56b12ef..9da185ccc 100644
--- a/gopls/internal/lsp/cache/cache.go
+++ b/gopls/internal/lsp/cache/cache.go
@@ -209,7 +209,7 @@ func (c *Cache) PackageStats(withNames bool) template.HTML {
typsCost := typesCost(v.pkg.types.Scope())
typInfoCost := typesInfoCost(v.pkg.typesInfo)
stat := packageStat{
- id: v.pkg.m.ID,
+ id: v.pkg.id,
mode: v.pkg.mode,
types: typsCost,
typesInfo: typInfoCost,
diff --git a/gopls/internal/lsp/cache/check.go b/gopls/internal/lsp/cache/check.go
index 722691d41..03691e50e 100644
--- a/gopls/internal/lsp/cache/check.go
+++ b/gopls/internal/lsp/cache/check.go
@@ -5,21 +5,21 @@
package cache
import (
- "bytes"
"context"
+ "crypto/sha256"
"errors"
"fmt"
"go/ast"
"go/types"
"path/filepath"
"regexp"
+ "sort"
"strings"
"sync"
"golang.org/x/mod/module"
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/packages"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
@@ -42,12 +42,14 @@ type packageKey struct {
type packageHandleKey source.Hash
-// A packageHandle is a handle to the future result of type-checking a package.
-// The resulting package is obtained from the await() method.
+// A packageHandle holds package information, some of which may not be fully
+// evaluated.
+//
+// The only methods on packageHandle that are safe to call before calling await
+// are Metadata and await itself.
type packageHandle struct {
- promise *memoize.Promise // [typeCheckResult]
-
- // m is the metadata associated with the package.
+ // TODO(rfindley): remove metadata from packageHandle. It is only used for
+ // bug detection.
m *source.Metadata
// key is the hashed key for the package.
@@ -58,12 +60,29 @@ type packageHandle struct {
// enough. (The key for analysis actions could similarly
// hash only Facts of direct dependencies.)
key packageHandleKey
+
+ // The shared type-checking promise.
+ promise *memoize.Promise // [typeCheckResult]
+}
+
+// typeCheckInputs contains the inputs of a call to typeCheckImpl, which
+// type-checks a package.
+type typeCheckInputs struct {
+ id PackageID
+ pkgPath PackagePath
+ name PackageName
+ mode source.ParseMode
+ goFiles, compiledGoFiles []source.FileHandle
+ sizes types.Sizes
+ deps map[PackageID]*packageHandle
+ depsByImpPath map[ImportPath]PackageID
+ goVersion string // packages.Module.GoVersion, e.g. "1.18"
}
// typeCheckResult contains the result of a call to
// typeCheckImpl, which type-checks a package.
type typeCheckResult struct {
- pkg *pkg
+ pkg *syntaxPackage
err error
}
@@ -101,7 +120,6 @@ func (s *snapshot) buildPackageHandle(ctx context.Context, id PackageID, mode so
// for each package is computed by at most one thread, then do
// the recursive key building of dependencies in parallel.
deps := make(map[PackageID]*packageHandle)
- var depKey source.Hash // XOR of all unique deps
for _, depID := range m.DepsByPkgPath {
depHandle, err := s.buildPackageHandle(ctx, depID, s.workspaceParseMode(depID))
// Don't use invalid metadata for dependencies if the top-level
@@ -121,9 +139,6 @@ func (s *snapshot) buildPackageHandle(ctx context.Context, id PackageID, mode so
// checking the entire package. Leave depKeys[i] unset.
continue
}
-
- depKey.XORWith(source.Hash(depHandle.key))
-
deps[depID] = depHandle
}
@@ -139,12 +154,29 @@ func (s *snapshot) buildPackageHandle(ctx context.Context, id PackageID, mode so
return nil, err
}
+ goVersion := ""
+ if m.Module != nil && m.Module.GoVersion != "" {
+ goVersion = m.Module.GoVersion
+ }
+
+ inputs := typeCheckInputs{
+ id: m.ID,
+ pkgPath: m.PkgPath,
+ name: m.Name,
+ mode: mode,
+ goFiles: goFiles,
+ compiledGoFiles: compiledGoFiles,
+ sizes: m.TypesSizes,
+ deps: deps,
+ depsByImpPath: m.DepsByImpPath,
+ goVersion: goVersion,
+ }
+
// All the file reading has now been done.
// Create a handle for the result of type checking.
- experimentalKey := s.View().Options().ExperimentalPackageCacheKey
- phKey := computePackageKey(m.ID, compiledGoFiles, m, depKey, mode, experimentalKey)
+ phKey := computePackageKey(s, inputs)
promise, release := s.store.Promise(phKey, func(ctx context.Context, arg interface{}) interface{} {
- pkg, err := typeCheckImpl(ctx, arg.(*snapshot), goFiles, compiledGoFiles, m, mode, deps)
+ pkg, err := typeCheckImpl(ctx, arg.(*snapshot), inputs)
return typeCheckResult{pkg, err}
})
@@ -165,10 +197,11 @@ func (s *snapshot) buildPackageHandle(ctx context.Context, id PackageID, mode so
// handles for type-checking its immediate deps, at which
// point there will be no need to even access s.meta.)
if s.meta.metadata[ph.m.ID] != ph.m {
+ // TODO(rfindley): this should be bug.Errorf.
return nil, fmt.Errorf("stale metadata for %s", ph.m.ID)
}
- // Check cache again in case another thread got there first.
+ // Check cache again in case another goroutine got there first.
if prev, ok := s.packages.Get(packageKey); ok {
prevPH := prev.(*packageHandle)
release()
@@ -223,61 +256,64 @@ func (s *snapshot) workspaceParseMode(id PackageID) source.ParseMode {
// computePackageKey returns a key representing the act of type checking
// a package named id containing the specified files, metadata, and
// combined dependency hash.
-func computePackageKey(id PackageID, files []source.FileHandle, m *source.Metadata, depsKey source.Hash, mode source.ParseMode, experimentalKey bool) packageHandleKey {
- // TODO(adonovan): opt: no need to materalize the bytes; hash them directly.
- // Also, use field separators to avoid spurious collisions.
- b := bytes.NewBuffer(nil)
- b.WriteString(string(id))
- if m.Module != nil {
- b.WriteString(m.Module.GoVersion) // go version affects type check errors.
- }
- if !experimentalKey {
- // cfg was used to produce the other hashed inputs (package ID, parsed Go
- // files, and deps). It should not otherwise affect the inputs to the type
- // checker, so this experiment omits it. This should increase cache hits on
- // the daemon as cfg contains the environment and working directory.
- hc := hashConfig(m.Config)
- b.Write(hc[:])
- }
- b.WriteByte(byte(mode))
- b.Write(depsKey[:])
- for _, file := range files {
- b.WriteString(file.FileIdentity().String())
- }
- // Metadata errors are interpreted and memoized on the computed package, so
- // we must hash them into the key here.
- //
- // TODO(rfindley): handle metadata diagnostics independently from
- // type-checking diagnostics.
- for _, err := range m.Errors {
- b.WriteString(err.Msg)
- b.WriteString(err.Pos)
- b.WriteRune(rune(err.Kind))
- }
- return packageHandleKey(source.HashOf(b.Bytes()))
-}
+func computePackageKey(s *snapshot, inputs typeCheckInputs) packageHandleKey {
+ hasher := sha256.New()
+
+ // In principle, a key must be the hash of an
+ // unambiguous encoding of all the relevant data.
+ // If it's ambiguous, we risk collisons.
+
+ // package identifiers
+ fmt.Fprintf(hasher, "package: %s %s %s\n", inputs.id, inputs.name, inputs.pkgPath)
+
+ // module Go version
+ fmt.Fprintf(hasher, "go %s\n", inputs.goVersion)
+
+ // parse mode
+ fmt.Fprintf(hasher, "mode %d\n", inputs.mode)
-// hashConfig returns the hash for the *packages.Config.
-func hashConfig(config *packages.Config) source.Hash {
- // TODO(adonovan): opt: don't materialize the bytes; hash them directly.
- // Also, use sound field separators to avoid collisions.
- var b bytes.Buffer
+ // import map
+ importPaths := make([]string, 0, len(inputs.depsByImpPath))
+ for impPath := range inputs.depsByImpPath {
+ importPaths = append(importPaths, string(impPath))
+ }
+ sort.Strings(importPaths)
+ for _, impPath := range importPaths {
+ fmt.Fprintf(hasher, "import %s %s", impPath, string(inputs.depsByImpPath[ImportPath(impPath)]))
+ }
- // Dir, Mode, Env, BuildFlags are the parts of the config that can change.
- b.WriteString(config.Dir)
- b.WriteRune(rune(config.Mode))
+ // deps, in PackageID order
+ depIDs := make([]string, 0, len(inputs.deps))
+ for depID := range inputs.deps {
+ depIDs = append(depIDs, string(depID))
+ }
+ sort.Strings(depIDs)
+ for _, depID := range depIDs {
+ dep := inputs.deps[PackageID(depID)]
+ fmt.Fprintf(hasher, "dep: %s key:%s\n", dep.m.PkgPath, dep.key)
+ }
- for _, e := range config.Env {
- b.WriteString(e)
+ // file names and contents
+ fmt.Fprintf(hasher, "compiledGoFiles: %d\n", len(inputs.compiledGoFiles))
+ for _, fh := range inputs.compiledGoFiles {
+ fmt.Fprintln(hasher, fh.FileIdentity())
}
- for _, f := range config.BuildFlags {
- b.WriteString(f)
+ fmt.Fprintf(hasher, "goFiles: %d\n", len(inputs.goFiles))
+ for _, fh := range inputs.goFiles {
+ fmt.Fprintln(hasher, fh.FileIdentity())
}
- return source.HashOf(b.Bytes())
+
+ // types sizes
+ sz := inputs.sizes.(*types.StdSizes)
+ fmt.Fprintf(hasher, "sizes: %d %d\n", sz.WordSize, sz.MaxAlign)
+
+ var hash [sha256.Size]byte
+ hasher.Sum(hash[:0])
+ return packageHandleKey(hash)
}
// await waits for typeCheckImpl to complete and returns its result.
-func (ph *packageHandle) await(ctx context.Context, s *snapshot) (*pkg, error) {
+func (ph *packageHandle) await(ctx context.Context, s *snapshot) (*syntaxPackage, error) {
v, err := s.awaitPromise(ctx, ph.promise)
if err != nil {
return nil, err
@@ -286,15 +322,7 @@ func (ph *packageHandle) await(ctx context.Context, s *snapshot) (*pkg, error) {
return data.pkg, data.err
}
-func (ph *packageHandle) CompiledGoFiles() []span.URI {
- return ph.m.CompiledGoFiles
-}
-
-func (ph *packageHandle) ID() string {
- return string(ph.m.ID)
-}
-
-func (ph *packageHandle) cached() (*pkg, error) {
+func (ph *packageHandle) cached() (*syntaxPackage, error) {
v := ph.promise.Cached()
if v == nil {
return nil, fmt.Errorf("no cached type information for %s", ph.m.PkgPath)
@@ -306,13 +334,13 @@ func (ph *packageHandle) cached() (*pkg, error) {
// typeCheckImpl type checks the parsed source files in compiledGoFiles.
// (The resulting pkg also holds the parsed but not type-checked goFiles.)
// deps holds the future results of type-checking the direct dependencies.
-func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFiles []source.FileHandle, m *source.Metadata, mode source.ParseMode, deps map[PackageID]*packageHandle) (*pkg, error) {
+func typeCheckImpl(ctx context.Context, snapshot *snapshot, inputs typeCheckInputs) (*syntaxPackage, error) {
// Start type checking of direct dependencies,
// in parallel and asynchronously.
// As the type checker imports each of these
// packages, it will wait for its completion.
var wg sync.WaitGroup
- for _, dep := range deps {
+ for _, dep := range inputs.deps {
wg.Add(1)
go func(dep *packageHandle) {
dep.await(ctx, snapshot) // ignore result
@@ -328,56 +356,39 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
defer wg.Wait()
var filter *unexportedFilter
- if mode == source.ParseExported {
+ if inputs.mode == source.ParseExported {
filter = &unexportedFilter{uses: map[string]bool{}}
}
- pkg, err := doTypeCheck(ctx, snapshot, goFiles, compiledGoFiles, m, mode, deps, filter)
+ pkg, err := doTypeCheck(ctx, snapshot, inputs, filter)
if err != nil {
return nil, err
}
- if mode == source.ParseExported {
+ if inputs.mode == source.ParseExported {
// The AST filtering is a little buggy and may remove things it
// shouldn't. If we only got undeclared name errors, try one more
// time keeping those names.
missing, unexpected := filter.ProcessErrors(pkg.typeErrors)
if len(unexpected) == 0 && len(missing) != 0 {
- pkg, err = doTypeCheck(ctx, snapshot, goFiles, compiledGoFiles, m, mode, deps, filter)
+ pkg, err = doTypeCheck(ctx, snapshot, inputs, filter)
if err != nil {
return nil, err
}
missing, unexpected = filter.ProcessErrors(pkg.typeErrors)
}
if len(unexpected) != 0 || len(missing) != 0 {
- pkg, err = doTypeCheck(ctx, snapshot, goFiles, compiledGoFiles, m, mode, deps, nil)
+ pkg, err = doTypeCheck(ctx, snapshot, inputs, nil)
if err != nil {
return nil, err
}
}
}
- // If this is a replaced module in the workspace, the version is
- // meaningless, and we don't want clients to access it.
- if m.Module != nil {
- pkg.version = &module.Version{
- Path: m.Module.Path,
- Version: m.Module.Version,
- }
- }
// We don't care about a package's errors unless we have parsed it in full.
- if mode != source.ParseFull {
+ if inputs.mode != source.ParseFull {
return pkg, nil
}
- for _, e := range m.Errors {
- diags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(pkg.ID())))
- continue
- }
- pkg.diagnostics = append(pkg.diagnostics, diags...)
- }
-
// Our heuristic for whether to show type checking errors is:
// + If any file was 'fixed', don't show type checking errors as we
// can't guarantee that they reference accurate locations in the source.
@@ -394,7 +405,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
for _, e := range pkg.parseErrors {
diags, err := parseErrorDiagnostics(snapshot, pkg, e)
if err != nil {
- event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(string(pkg.ID())))
+ event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(string(inputs.id)))
continue
}
for _, diag := range diags {
@@ -412,7 +423,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
for _, e := range expandErrors(unexpanded, snapshot.View().Options().RelatedInformationSupported) {
diags, err := typeErrorDiagnostics(snapshot, pkg, e)
if err != nil {
- event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(string(pkg.ID())))
+ event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(string(inputs.id)))
continue
}
pkg.typeErrors = append(pkg.typeErrors, e.primary)
@@ -426,27 +437,20 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
}
}
- depsErrors, err := snapshot.depsErrors(ctx, pkg)
- if err != nil {
- return nil, err
- }
- pkg.diagnostics = append(pkg.diagnostics, depsErrors...)
-
return pkg, nil
}
var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
-func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFiles []source.FileHandle, m *source.Metadata, mode source.ParseMode, deps map[PackageID]*packageHandle, astFilter *unexportedFilter) (*pkg, error) {
- ctx, done := event.Start(ctx, "cache.typeCheck", tag.Package.Of(string(m.ID)))
+func doTypeCheck(ctx context.Context, snapshot *snapshot, inputs typeCheckInputs, astFilter *unexportedFilter) (*syntaxPackage, error) {
+ ctx, done := event.Start(ctx, "cache.typeCheck", tag.Package.Of(string(inputs.id)))
defer done()
- pkg := &pkg{
- m: m,
- mode: mode,
+ pkg := &syntaxPackage{
+ id: inputs.id,
+ mode: inputs.mode,
fset: snapshot.FileSet(), // must match parse call below (snapshot.ParseGo for now)
- deps: make(map[PackageID]*pkg),
- types: types.NewPackage(string(m.PkgPath), string(m.Name)),
+ types: types.NewPackage(string(inputs.pkgPath), string(inputs.name)),
typesInfo: &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
@@ -461,9 +465,9 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
// Parse the non-compiled GoFiles. (These aren't presented to
// the type checker but are part of the returned pkg.)
// TODO(adonovan): opt: parallelize parsing.
- for _, fh := range goFiles {
- goMode := mode
- if mode == source.ParseExported {
+ for _, fh := range inputs.goFiles {
+ goMode := inputs.mode
+ if inputs.mode == source.ParseExported {
// This package is being loaded only for type information,
// to which non-compiled Go files are irrelevant,
// so parse only the header.
@@ -477,40 +481,32 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
}
// Parse the CompiledGoFiles: those seen by the compiler/typechecker.
- if err := parseCompiledGoFiles(ctx, compiledGoFiles, snapshot, mode, pkg, astFilter); err != nil {
+ if err := parseCompiledGoFiles(ctx, inputs.compiledGoFiles, snapshot, inputs.mode, pkg, astFilter); err != nil {
return nil, err
}
// Use the default type information for the unsafe package.
- if m.PkgPath == "unsafe" {
+ if inputs.pkgPath == "unsafe" {
// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
// race to Unsafe.completed.
// TODO(adonovan): factor (tail-merge) with the normal control path.
pkg.types = types.Unsafe
pkg.methodsets = methodsets.NewIndex(pkg.fset, pkg.types)
- pkg.xrefs = xrefs.Index(pkg)
+ pkg.xrefs = xrefs.Index(pkg.compiledGoFiles, pkg.types, pkg.typesInfo)
return pkg, nil
}
- if len(m.CompiledGoFiles) == 0 {
- // No files most likely means go/packages failed. Try to attach error
- // messages to the file as much as possible.
- var found bool
- for _, e := range m.Errors {
- srcDiags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- continue
- }
- found = true
- pkg.diagnostics = append(pkg.diagnostics, srcDiags...)
- }
- if found {
- return pkg, nil
- }
- return nil, fmt.Errorf("no parsed files for package %s, expected: %v, errors: %v", pkg.m.PkgPath, pkg.compiledGoFiles, m.Errors)
+ if len(pkg.compiledGoFiles) == 0 {
+ // No files most likely means go/packages failed.
+ //
+ // TODO(rfindley): in the past, we would capture go list errors in this
+ // case, to present go list errors to the user. However we had no tests for
+ // this behavior. It is unclear if anything better can be done here.
+ return nil, fmt.Errorf("no parsed files for package %s", inputs.pkgPath)
}
cfg := &types.Config{
+ Sizes: inputs.sizes,
Error: func(e error) {
pkg.typeErrors = append(pkg.typeErrors, e.(types.Error))
},
@@ -519,30 +515,30 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
// based on the metadata before we start type checking,
// reporting them via types.Importer places the errors
// at the correct source location.
- id, ok := pkg.m.DepsByImpPath[ImportPath(path)]
+ id, ok := inputs.depsByImpPath[ImportPath(path)]
if !ok {
// If the import declaration is broken,
// go list may fail to report metadata about it.
// See TestFixImportDecl for an example.
return nil, fmt.Errorf("missing metadata for import of %q", path)
}
- dep, ok := deps[id] // id may be ""
+ dep, ok := inputs.deps[id] // id may be ""
if !ok {
return nil, snapshot.missingPkgError(path)
}
- if !source.IsValidImport(m.PkgPath, dep.m.PkgPath) {
+ if !source.IsValidImport(inputs.pkgPath, dep.m.PkgPath) {
return nil, fmt.Errorf("invalid use of internal package %s", path)
}
depPkg, err := dep.await(ctx, snapshot)
if err != nil {
return nil, err
}
- pkg.deps[depPkg.m.ID] = depPkg
return depPkg.types, nil
}),
}
- if pkg.m.Module != nil && pkg.m.Module.GoVersion != "" {
- goVersion := "go" + pkg.m.Module.GoVersion
+
+ if inputs.goVersion != "" {
+ goVersion := "go" + inputs.goVersion
// types.NewChecker panics if GoVersion is invalid. An unparsable mod
// file should probably stop us before we get here, but double check
// just in case.
@@ -551,7 +547,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
}
}
- if mode != source.ParseFull {
+ if inputs.mode != source.ParseFull {
cfg.DisableUnusedImportCheck = true
cfg.IgnoreFuncBodies = true
}
@@ -574,7 +570,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
pkg.methodsets = methodsets.NewIndex(pkg.fset, pkg.types)
// Build global index of outbound cross-references.
- pkg.xrefs = xrefs.Index(pkg)
+ pkg.xrefs = xrefs.Index(pkg.compiledGoFiles, pkg.types, pkg.typesInfo)
// If the context was cancelled, we may have returned a ton of transient
// errors to the type checker. Swallow them.
@@ -584,7 +580,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
return pkg, nil
}
-func parseCompiledGoFiles(ctx context.Context, compiledGoFiles []source.FileHandle, snapshot *snapshot, mode source.ParseMode, pkg *pkg, astFilter *unexportedFilter) error {
+func parseCompiledGoFiles(ctx context.Context, compiledGoFiles []source.FileHandle, snapshot *snapshot, mode source.ParseMode, pkg *syntaxPackage, astFilter *unexportedFilter) error {
// TODO(adonovan): opt: parallelize this loop, which takes 1-25ms.
for _, fh := range compiledGoFiles {
var pgf *source.ParsedGoFile
@@ -633,11 +629,16 @@ func parseCompiledGoFiles(ctx context.Context, compiledGoFiles []source.FileHand
return nil
}
-func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.Diagnostic, error) {
+// depsErrors creates diagnostics for each metadata error (e.g. import cycle).
+// These may be attached to import declarations in the transitive source files
+// of pkg, or to 'requires' declarations in the package's go.mod file.
+//
+// TODO(rfindley): move this to errors.go
+func (s *snapshot) depsErrors(ctx context.Context, pkg *syntaxPackage, depsErrors []*packagesinternal.PackageError) ([]*source.Diagnostic, error) {
// Select packages that can't be found, and were imported in non-workspace packages.
// Workspace packages already show their own errors.
var relevantErrors []*packagesinternal.PackageError
- for _, depsError := range pkg.m.DepsErrors {
+ for _, depsError := range depsErrors {
// Up to Go 1.15, the missing package was included in the stack, which
// was presumably a bug. We want the next one up.
directImporterIdx := len(depsError.ImportStack) - 1
@@ -665,7 +666,7 @@ func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.Diagnost
allImports := map[string][]fileImport{}
for _, cgf := range pkg.compiledGoFiles {
// TODO(adonovan): modify Imports() to accept a single token.File (cgf.Tok).
- for _, group := range astutil.Imports(s.FileSet(), cgf.File) {
+ for _, group := range astutil.Imports(pkg.fset, cgf.File) {
for _, imp := range group {
if imp.Path == nil {
continue
diff --git a/gopls/internal/lsp/cache/errors.go b/gopls/internal/lsp/cache/errors.go
index 8af1ac355..b0babc7b0 100644
--- a/gopls/internal/lsp/cache/errors.go
+++ b/gopls/internal/lsp/cache/errors.go
@@ -28,30 +28,20 @@ import (
"golang.org/x/tools/internal/typesinternal"
)
-func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) {
- if msg, spn, ok := parseGoListImportCycleError(snapshot, e, pkg); ok {
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- return []*source.Diagnostic{{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: msg,
- }}, nil
+func goPackagesErrorDiagnostics(e packages.Error, pkg *syntaxPackage, fromDir string) (diags []*source.Diagnostic, rerr error) {
+ if diag, ok := parseGoListImportCycleError(e, pkg); ok {
+ return []*source.Diagnostic{diag}, nil
}
var spn span.Span
if e.Pos == "" {
- spn = parseGoListError(e.Msg, pkg.m.Config.Dir)
+ spn = parseGoListError(e.Msg, fromDir)
// We may not have been able to parse a valid span. Apply the errors to all files.
if _, err := spanToRange(pkg, spn); err != nil {
var diags []*source.Diagnostic
- for _, cgf := range pkg.compiledGoFiles {
+ for _, pgf := range pkg.compiledGoFiles {
diags = append(diags, &source.Diagnostic{
- URI: cgf.URI,
+ URI: pgf.URI,
Severity: protocol.SeverityError,
Source: source.ListError,
Message: e.Msg,
@@ -60,9 +50,20 @@ func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error)
return diags, nil
}
} else {
- spn = span.ParseInDir(e.Pos, pkg.m.Config.Dir)
+ spn = span.ParseInDir(e.Pos, fromDir)
}
+ // TODO(rfindley): in some cases the go command outputs invalid spans, for
+ // example (from TestGoListErrors):
+ //
+ // package a
+ // import
+ //
+ // In this case, the go command will complain about a.go:2:8, which is after
+ // the trailing newline but still considered to be on the second line, most
+ // likely because *token.File lacks information about newline termination.
+ //
+ // We could do better here by handling that case.
rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
@@ -76,7 +77,7 @@ func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error)
}}, nil
}
-func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
+func parseErrorDiagnostics(snapshot *snapshot, pkg *syntaxPackage, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
// The first parser error is likely the root cause of the problem.
if errList.Len() <= 0 {
return nil, fmt.Errorf("no errors in %v", errList)
@@ -102,7 +103,7 @@ func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorLi
var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`)
-func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
+func typeErrorDiagnostics(snapshot *snapshot, pkg *syntaxPackage, e extendedError) ([]*source.Diagnostic, error) {
code, loc, err := typeErrorData(pkg, e.primary)
if err != nil {
return nil, err
@@ -286,7 +287,7 @@ func relatedInformation(diag *gobDiagnostic) []source.RelatedInformation {
return out
}
-func typeErrorData(pkg *pkg, terr types.Error) (typesinternal.ErrorCode, protocol.Location, error) {
+func typeErrorData(pkg *syntaxPackage, terr types.Error) (typesinternal.ErrorCode, protocol.Location, error) {
ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
if !ok {
start, end = terr.Pos, terr.Pos
@@ -299,7 +300,7 @@ func typeErrorData(pkg *pkg, terr types.Error) (typesinternal.ErrorCode, protoco
}
// go/types errors retain their FileSet.
// Sanity-check that we're using the right one.
- fset := pkg.FileSet()
+ fset := pkg.fset
if fset != terr.Fset {
return 0, protocol.Location{}, bug.Errorf("wrong FileSet for type error")
}
@@ -320,7 +321,7 @@ func typeErrorData(pkg *pkg, terr types.Error) (typesinternal.ErrorCode, protoco
// spanToRange converts a span.Span to a protocol.Range,
// assuming that the span belongs to the package whose diagnostics are being computed.
-func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) {
+func spanToRange(pkg *syntaxPackage, spn span.Span) (protocol.Range, error) {
pgf, err := pkg.File(spn.URI())
if err != nil {
return protocol.Range{}, err
@@ -344,36 +345,39 @@ func parseGoListError(input, wd string) span.Span {
return span.ParseInDir(input[:msgIndex], wd)
}
-func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
+func parseGoListImportCycleError(e packages.Error, pkg *syntaxPackage) (*source.Diagnostic, bool) {
re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
if len(matches) < 3 {
- return e.Msg, span.Span{}, false
+ return nil, false
}
msg := matches[1]
importList := strings.Split(matches[2], " ")
// Since the error is relative to the current package. The import that is causing
// the import cycle error is the second one in the list.
if len(importList) < 2 {
- return msg, span.Span{}, false
+ return nil, false
}
// Imports have quotation marks around them.
circImp := strconv.Quote(importList[1])
- for _, cgf := range pkg.compiledGoFiles {
+ for _, pgf := range pkg.compiledGoFiles {
// Search file imports for the import that is causing the import cycle.
- for _, imp := range cgf.File.Imports {
+ for _, imp := range pgf.File.Imports {
if imp.Path.Value == circImp {
- start, end, err := safetoken.Offsets(cgf.Tok, imp.Pos(), imp.End())
- if err != nil {
- return msg, span.Span{}, false
- }
- spn, err := cgf.Mapper.OffsetSpan(start, end)
+ rng, err := pgf.PosMappedRange(imp.Pos(), imp.End())
if err != nil {
- return msg, span.Span{}, false
+ return nil, false
}
- return msg, spn, true
+
+ return &source.Diagnostic{
+ URI: pgf.URI,
+ Range: rng.Range(),
+ Severity: protocol.SeverityError,
+ Source: source.ListError,
+ Message: msg,
+ }, true
}
}
}
- return msg, span.Span{}, false
+ return nil, false
}
diff --git a/gopls/internal/lsp/cache/error_test.go b/gopls/internal/lsp/cache/errors_test.go
index 43cc03f78..43cc03f78 100644
--- a/gopls/internal/lsp/cache/error_test.go
+++ b/gopls/internal/lsp/cache/errors_test.go
diff --git a/gopls/internal/lsp/cache/load.go b/gopls/internal/lsp/cache/load.go
index 1d69504a9..9b321cec5 100644
--- a/gopls/internal/lsp/cache/load.go
+++ b/gopls/internal/lsp/cache/load.go
@@ -447,21 +447,13 @@ func buildMetadata(ctx context.Context, pkg *packages.Package, cfg *packages.Con
Name: PackageName(pkg.Name),
ForTest: PackagePath(packagesinternal.GetForTest(pkg)),
TypesSizes: pkg.TypesSizes,
- Config: cfg,
+ LoadDir: cfg.Dir,
Module: pkg.Module,
+ Errors: pkg.Errors,
DepsErrors: packagesinternal.GetDepsErrors(pkg),
}
- updates[id] = m
- for _, err := range pkg.Errors {
- // Filter out parse errors from go list. We'll get them when we
- // actually parse, and buggy overlay support may generate spurious
- // errors. (See TestNewModule_Issue38207.)
- if strings.Contains(err.Msg, "expected '") {
- continue
- }
- m.Errors = append(m.Errors, err)
- }
+ updates[id] = m
for _, filename := range pkg.CompiledGoFiles {
uri := span.URIFromPath(filename)
diff --git a/gopls/internal/lsp/cache/pkg.go b/gopls/internal/lsp/cache/pkg.go
index 37aae50a0..bb4823c03 100644
--- a/gopls/internal/lsp/cache/pkg.go
+++ b/gopls/internal/lsp/cache/pkg.go
@@ -5,19 +5,22 @@
package cache
import (
+ "context"
"fmt"
"go/ast"
"go/scanner"
"go/token"
"go/types"
+ "strings"
- "golang.org/x/mod/module"
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
"golang.org/x/tools/gopls/internal/lsp/source/xrefs"
"golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/event/tag"
"golang.org/x/tools/internal/memoize"
)
@@ -29,30 +32,93 @@ type (
ImportPath = source.ImportPath
)
-// pkg contains parse trees and type information for a package.
-type pkg struct {
+// A Package is the union of snapshot-local information (Metadata) and shared
+// type-checking information (a syntaxPackage).
+//
+// TODO(rfindley): for now, we do not persist the post-processing of
+// loadDiagnostics, because the value of the snapshot.packages map is just the
+// package handle. Fix this.
+type Package struct {
m *source.Metadata
- mode source.ParseMode
+ pkg *syntaxPackage
+ loadDiagnostics *memoize.Promise // post-processed errors from loading
+}
+
+func newPackage(m *source.Metadata, pkg *syntaxPackage) *Package {
+ p := &Package{
+ m: m,
+ pkg: pkg,
+ }
+ if len(m.Errors) > 0 || len(m.DepsErrors) > 0 {
+ p.loadDiagnostics = memoize.NewPromise(fmt.Sprintf("loadDiagnostics(%s)", m.ID), func(ctx context.Context, arg interface{}) interface{} {
+ s := arg.(*snapshot)
+ var diags []*source.Diagnostic
+ for _, packagesErr := range p.m.Errors {
+ // Filter out parse errors from go list. We'll get them when we
+ // actually parse, and buggy overlay support may generate spurious
+ // errors. (See TestNewModule_Issue38207.)
+ if strings.Contains(packagesErr.Msg, "expected '") {
+ continue
+ }
+ pkgDiags, err := goPackagesErrorDiagnostics(packagesErr, p.pkg, p.m.LoadDir)
+ if err != nil {
+ // There are certain cases where the go command returns invalid
+ // positions, so we cannot panic or even bug.Reportf here.
+ event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(p.m.ID)))
+ continue
+ }
+ diags = append(diags, pkgDiags...)
+ }
+
+ // TODO(rfindley): this is buggy: an insignificant change to a modfile
+ // (or an unsaved modfile) could affect the position of deps errors,
+ // without invalidating the package.
+ depsDiags, err := s.depsErrors(ctx, p.pkg, p.m.DepsErrors)
+ if err != nil {
+ if ctx.Err() == nil {
+ // TODO(rfindley): consider making this a bug.Reportf. depsErrors should
+ // not normally fail.
+ event.Error(ctx, "unable to compute deps errors", err, tag.Package.Of(string(p.m.ID)))
+ }
+ return nil
+ }
+ diags = append(diags, depsDiags...)
+ return diags
+ })
+ }
+ return p
+}
+
+// syntaxPackage contains parse trees and type information for a package.
+type syntaxPackage struct {
+ // -- identifiers --
+ id PackageID
+ mode source.ParseMode
+
+ // -- outputs --
fset *token.FileSet // for now, same as the snapshot's FileSet
goFiles []*source.ParsedGoFile
compiledGoFiles []*source.ParsedGoFile
diagnostics []*source.Diagnostic
- deps map[PackageID]*pkg // use m.DepsBy{Pkg,Imp}Path to look up ID
- version *module.Version // may be nil; may differ from m.Module.Version
parseErrors []scanner.ErrorList
typeErrors []types.Error
types *types.Package
typesInfo *types.Info
- hasFixedFiles bool // if true, AST was sufficiently mangled that we should hide type errors
- xrefs []byte // serializable index of outbound cross-references
-
- analyses memoize.Store // maps analyzer.Name to Promise[actionResult]
- methodsets *methodsets.Index // index of method sets of package-level types
+ hasFixedFiles bool // if true, AST was sufficiently mangled that we should hide type errors
+ xrefs []byte // serializable index of outbound cross-references
+ analyses memoize.Store // maps analyzer.Name to Promise[actionResult]
+ methodsets *methodsets.Index // index of method sets of package-level types
}
-func (p *pkg) String() string { return string(p.ID()) }
+func (p *Package) String() string { return string(p.m.ID) }
+
+func (p *Package) Metadata() *source.Metadata {
+ return p.m
+}
// A loadScope defines a package loading scope for use with go/packages.
+//
+// TODO(rfindley): move this to load.go.
type loadScope interface {
aScope()
}
@@ -70,127 +136,96 @@ func (packageLoadScope) aScope() {}
func (moduleLoadScope) aScope() {}
func (viewLoadScope) aScope() {}
-func (p *pkg) ID() PackageID { return p.m.ID }
-func (p *pkg) Name() PackageName { return p.m.Name }
-func (p *pkg) PkgPath() PackagePath { return p.m.PkgPath }
+func (p *Package) ParseMode() source.ParseMode {
+ return p.pkg.mode
+}
-func (p *pkg) ParseMode() source.ParseMode {
- return p.mode
+func (p *Package) CompiledGoFiles() []*source.ParsedGoFile {
+ return p.pkg.compiledGoFiles
}
-func (p *pkg) CompiledGoFiles() []*source.ParsedGoFile {
- return p.compiledGoFiles
+func (p *Package) File(uri span.URI) (*source.ParsedGoFile, error) {
+ return p.pkg.File(uri)
}
-func (p *pkg) File(uri span.URI) (*source.ParsedGoFile, error) {
- for _, cgf := range p.compiledGoFiles {
+func (pkg *syntaxPackage) File(uri span.URI) (*source.ParsedGoFile, error) {
+ for _, cgf := range pkg.compiledGoFiles {
if cgf.URI == uri {
return cgf, nil
}
}
- for _, gf := range p.goFiles {
+ for _, gf := range pkg.goFiles {
if gf.URI == uri {
return gf, nil
}
}
- return nil, fmt.Errorf("no parsed file for %s in %v", uri, p.m.ID)
+ return nil, fmt.Errorf("no parsed file for %s in %v", uri, pkg.id)
}
-func (p *pkg) GetSyntax() []*ast.File {
+func (p *Package) GetSyntax() []*ast.File {
var syntax []*ast.File
- for _, pgf := range p.compiledGoFiles {
+ for _, pgf := range p.pkg.compiledGoFiles {
syntax = append(syntax, pgf.File)
}
return syntax
}
-func (p *pkg) FileSet() *token.FileSet {
- return p.fset
+func (p *Package) FileSet() *token.FileSet {
+ return p.pkg.fset
}
-func (p *pkg) GetTypes() *types.Package {
- return p.types
+func (p *Package) GetTypes() *types.Package {
+ return p.pkg.types
}
-func (p *pkg) GetTypesInfo() *types.Info {
- return p.typesInfo
+func (p *Package) GetTypesInfo() *types.Info {
+ return p.pkg.typesInfo
}
-func (p *pkg) GetTypesSizes() types.Sizes {
- return p.m.TypesSizes
+func (p *Package) HasParseErrors() bool {
+ return len(p.pkg.parseErrors) != 0
}
-func (p *pkg) ForTest() string {
- return string(p.m.ForTest)
+func (p *Package) HasTypeErrors() bool {
+ return len(p.pkg.typeErrors) != 0
}
-// DirectDep returns the directly imported dependency of this package,
-// given its PackagePath. (If you have an ImportPath, e.g. a string
-// from an import declaration, use ResolveImportPath instead.
-// They may differ in case of vendoring.)
-func (p *pkg) DirectDep(pkgPath PackagePath) (source.Package, error) {
- if id, ok := p.m.DepsByPkgPath[pkgPath]; ok {
- if imp := p.deps[id]; imp != nil {
- return imp, nil
+func (p *Package) DiagnosticsForFile(ctx context.Context, s source.Snapshot, uri span.URI) ([]*source.Diagnostic, error) {
+ var diags []*source.Diagnostic
+ for _, diag := range p.pkg.diagnostics {
+ if diag.URI == uri {
+ diags = append(diags, diag)
}
}
- return nil, fmt.Errorf("package does not import package with path %s", pkgPath)
-}
-// ResolveImportPath returns the directly imported dependency of this package,
-// given its ImportPath. See also DirectDep.
-func (p *pkg) ResolveImportPath(importPath ImportPath) (source.Package, error) {
- if id, ok := p.m.DepsByImpPath[importPath]; ok && id != "" {
- if imp := p.deps[id]; imp != nil {
- return imp, nil
+ if p.loadDiagnostics != nil {
+ res, err := p.loadDiagnostics.Get(ctx, s)
+ if err != nil {
+ return nil, err
}
- }
- return nil, fmt.Errorf("package does not import %s", importPath)
-}
-
-func (p *pkg) Imports() []source.Package {
- var result []source.Package // unordered
- for _, dep := range p.deps {
- result = append(result, dep)
- }
- return result
-}
-
-func (p *pkg) Version() *module.Version {
- return p.version
-}
-
-func (p *pkg) HasListOrParseErrors() bool {
- return len(p.m.Errors) != 0 || len(p.parseErrors) != 0
-}
-
-func (p *pkg) HasTypeErrors() bool {
- return len(p.typeErrors) != 0
-}
-
-func (p *pkg) DiagnosticsForFile(uri span.URI) []*source.Diagnostic {
- var res []*source.Diagnostic
- for _, diag := range p.diagnostics {
- if diag.URI == uri {
- res = append(res, diag)
+ for _, diag := range res.([]*source.Diagnostic) {
+ if diag.URI == uri {
+ diags = append(diags, diag)
+ }
}
}
- return res
+
+ return diags, nil
}
// 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 {
+func (p *Package) 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, targets)
+ return xrefs.Lookup(p.m, p.pkg.xrefs, targets)
}
-func (p *pkg) MethodSetsIndex() *methodsets.Index {
+func (p *Package) MethodSetsIndex() *methodsets.Index {
// TODO(adonovan): In future, p.methodsets will be retrieved from a
// section of the cache file produced by type checking.
- return p.methodsets
+ return p.pkg.methodsets
}
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index 51d7a7964..995fba34c 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -118,7 +118,7 @@ type snapshot struct {
// analyses maps an analysisKey (which identifies a package
// and a set of analyzers) to the handle for the future result
// of loading the package and analyzing it.
- analyses *persistent.Map // from analysisKey to analysisHandle
+ analyses *persistent.Map // from analysisKey to analysisPromise
// workspacePackages contains the workspace's packages, which are loaded
// when the view is created.
@@ -626,10 +626,20 @@ func (s *snapshot) buildOverlay() map[string][]byte {
}
// TypeCheck type-checks the specified packages in the given mode.
+//
+// The resulting packages slice always contains len(ids) entries, though some
+// of them may be nil if (and only if) the resulting error is non-nil.
+//
+// An error is returned if any of the packages fail to type-check. This is
+// different from having type-checking errors: a failure to type-check
+// indicates context cancellation or otherwise significant failure to perform
+// the type-checking operation.
func (s *snapshot) TypeCheck(ctx context.Context, mode source.TypecheckMode, ids ...PackageID) ([]source.Package, error) {
// Build all the handles...
- var phs []*packageHandle
- for _, id := range ids {
+ phs := make([]*packageHandle, len(ids))
+ pkgs := make([]source.Package, len(ids))
+ var firstErr error
+ for i, id := range ids {
parseMode := source.ParseFull
if mode == source.TypecheckWorkspace {
parseMode = s.workspaceParseMode(id)
@@ -637,21 +647,36 @@ func (s *snapshot) TypeCheck(ctx context.Context, mode source.TypecheckMode, ids
ph, err := s.buildPackageHandle(ctx, id, parseMode)
if err != nil {
- return nil, err
+ if firstErr == nil {
+ firstErr = err
+ }
+ if ctx.Err() != nil {
+ return pkgs, firstErr
+ }
+ continue
}
- phs = append(phs, ph)
+ phs[i] = ph
}
// ...then await them all.
- var pkgs []source.Package
- for _, ph := range phs {
- pkg, err := ph.await(ctx, s)
+ for i, ph := range phs {
+ if ph == nil {
+ continue
+ }
+ p, err := ph.await(ctx, s)
if err != nil {
- return nil, err
+ if firstErr == nil {
+ firstErr = err
+ }
+ if ctx.Err() != nil {
+ return pkgs, firstErr
+ }
+ continue
}
- pkgs = append(pkgs, pkg)
+ pkgs[i] = newPackage(ph.m, p)
}
- return pkgs, nil
+
+ return pkgs, firstErr
}
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
@@ -1058,7 +1083,7 @@ func (s *snapshot) AllMetadata(ctx context.Context) ([]*source.Metadata, error)
return meta, nil
}
-func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]source.Package, error) {
+func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]*types.Package, error) {
// Don't reload workspace package metadata.
// This function is meant to only return currently cached information.
s.AwaitInitialized(ctx)
@@ -1066,24 +1091,41 @@ func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]sourc
s.mu.Lock()
defer s.mu.Unlock()
- results := map[PackagePath]source.Package{}
+ 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)
s.packages.Range(func(_, v interface{}) {
- cachedPkg, err := v.(*packageHandle).cached()
+ 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
}
- for _, newPkg := range cachedPkg.deps {
- pkgPath := newPkg.PkgPath()
- if oldPkg, ok := results[pkgPath]; ok {
- // Using the same trick as NarrowestPackage, prefer non-variants.
- if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
- results[pkgPath] = newPkg
- }
- } else {
- results[pkgPath] = newPkg
+ 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
}
})
+ results := make(map[PackagePath]*types.Package)
+ for pkgPath, pkg := range pkgs {
+ if imported[pkgPath] {
+ results[pkgPath] = pkg.types
+ }
+ }
+
return results, nil
}