2019-05-10 14:35:43 -06:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2019-03-04 15:01:39 -07:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
2019-07-09 15:52:23 -06:00
|
|
|
"bytes"
|
2019-03-05 15:30:44 -07:00
|
|
|
"context"
|
2019-10-01 13:21:06 -06:00
|
|
|
"fmt"
|
2019-03-04 15:01:39 -07:00
|
|
|
"go/ast"
|
2019-10-29 16:13:19 -06:00
|
|
|
"go/token"
|
2019-03-04 15:01:39 -07:00
|
|
|
"go/types"
|
2020-01-22 17:23:30 -07:00
|
|
|
"path"
|
2019-10-01 13:21:06 -06:00
|
|
|
"sort"
|
2020-02-06 11:59:27 -07:00
|
|
|
"strings"
|
2019-06-21 15:00:02 -06:00
|
|
|
"sync"
|
2019-03-04 15:01:39 -07:00
|
|
|
|
|
|
|
"golang.org/x/tools/go/packages"
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event"
|
2020-03-10 21:09:39 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
2019-06-21 15:00:02 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
2019-07-09 15:52:23 -06:00
|
|
|
"golang.org/x/tools/internal/memoize"
|
2019-11-21 12:54:31 -07:00
|
|
|
"golang.org/x/tools/internal/span"
|
2020-06-10 14:05:41 -06:00
|
|
|
"golang.org/x/tools/internal/typesinternal"
|
2019-08-06 13:13:11 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-03-04 15:01:39 -07:00
|
|
|
)
|
|
|
|
|
2020-02-03 15:07:45 -07:00
|
|
|
type packageHandleKey string
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// packageHandle implements source.PackageHandle.
|
2019-11-29 23:17:57 -07:00
|
|
|
type packageHandle struct {
|
2019-07-09 15:52:23 -06:00
|
|
|
handle *memoize.Handle
|
|
|
|
|
2020-04-07 20:54:42 -06:00
|
|
|
goFiles, compiledGoFiles []*parseGoHandle
|
2019-10-01 13:21:06 -06:00
|
|
|
|
|
|
|
// mode is the mode the the files were parsed in.
|
|
|
|
mode source.ParseMode
|
2019-07-09 15:52:23 -06:00
|
|
|
|
2019-10-01 13:21:06 -06:00
|
|
|
// m is the metadata associated with the package.
|
|
|
|
m *metadata
|
|
|
|
|
|
|
|
// key is the hashed key for the package.
|
2020-02-03 15:07:45 -07:00
|
|
|
key packageHandleKey
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) packageKey() packageKey {
|
2019-10-01 13:21:06 -06:00
|
|
|
return packageKey{
|
2019-11-29 23:17:57 -07:00
|
|
|
id: ph.m.id,
|
|
|
|
mode: ph.mode,
|
2019-09-19 14:53:53 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
// packageData contains the data produced by type-checking a package.
|
|
|
|
type packageData struct {
|
2019-07-09 15:52:23 -06:00
|
|
|
memoize.NoCopy
|
|
|
|
|
|
|
|
pkg *pkg
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// buildPackageHandle returns a source.PackageHandle for a given package and config.
|
2020-02-07 12:38:36 -07:00
|
|
|
func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
|
|
|
|
if ph := s.getPackage(id, mode); ph != nil {
|
2019-11-29 23:17:57 -07:00
|
|
|
return ph, nil
|
2019-10-01 13:21:06 -06:00
|
|
|
}
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// Build the PackageHandle for this ID and its dependencies.
|
2020-02-07 12:38:36 -07:00
|
|
|
ph, deps, err := s.buildKey(ctx, id, mode)
|
2019-10-01 13:21:06 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-29 10:16:15 -06:00
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
// Do not close over the packageHandle or the snapshot in the Bind function.
|
2019-10-29 10:16:15 -06:00
|
|
|
// This creates a cycle, which causes the finalizers to never run on the handles.
|
|
|
|
// The possible cycles are:
|
|
|
|
//
|
2019-11-29 23:17:57 -07:00
|
|
|
// packageHandle.h.function -> packageHandle
|
|
|
|
// packageHandle.h.function -> snapshot -> packageHandle
|
2019-10-29 10:16:15 -06:00
|
|
|
//
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
m := ph.m
|
|
|
|
goFiles := ph.goFiles
|
|
|
|
compiledGoFiles := ph.compiledGoFiles
|
|
|
|
key := ph.key
|
2019-10-29 10:16:15 -06:00
|
|
|
fset := s.view.session.cache.fset
|
|
|
|
|
2020-02-03 15:07:45 -07:00
|
|
|
h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
|
2019-10-20 22:00:20 -06:00
|
|
|
// Begin loading the direct dependencies, in parallel.
|
2019-10-29 10:16:15 -06:00
|
|
|
for _, dep := range deps {
|
2019-11-29 23:17:57 -07:00
|
|
|
go func(dep *packageHandle) {
|
2019-10-20 22:00:20 -06:00
|
|
|
dep.check(ctx)
|
|
|
|
}(dep)
|
|
|
|
}
|
2019-11-29 23:17:57 -07:00
|
|
|
data := &packageData{}
|
2019-11-21 12:54:31 -07:00
|
|
|
data.pkg, data.err = typeCheck(ctx, fset, m, mode, goFiles, compiledGoFiles, deps)
|
2019-10-01 13:21:06 -06:00
|
|
|
return data
|
|
|
|
})
|
2019-11-29 23:17:57 -07:00
|
|
|
ph.handle = h
|
2019-10-01 13:21:06 -06:00
|
|
|
|
2020-05-07 12:57:44 -06:00
|
|
|
// Cache the PackageHandle in the snapshot. If a package handle has already
|
|
|
|
// been cached, addPackage will return the cached value. This is fine,
|
|
|
|
// since the original package handle above will have no references and be
|
|
|
|
// garbage collected.
|
|
|
|
ph = s.addPackageHandle(ph)
|
2019-10-18 13:14:59 -06:00
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
return ph, nil
|
2019-10-01 13:21:06 -06:00
|
|
|
}
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// buildKey computes the key for a given packageHandle.
|
2020-02-07 12:38:36 -07:00
|
|
|
func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, map[packagePath]*packageHandle, error) {
|
2019-10-20 22:00:20 -06:00
|
|
|
m := s.getMetadata(id)
|
2019-09-27 11:17:59 -06:00
|
|
|
if m == nil {
|
2019-10-29 10:16:15 -06:00
|
|
|
return nil, nil, errors.Errorf("no metadata for %s", id)
|
2019-09-27 11:17:59 -06:00
|
|
|
}
|
2019-11-21 12:54:31 -07:00
|
|
|
goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode)
|
2019-05-24 14:03:39 -06:00
|
|
|
if err != nil {
|
2019-10-29 10:16:15 -06:00
|
|
|
return nil, nil, err
|
2019-05-24 14:03:39 -06:00
|
|
|
}
|
2019-11-29 23:17:57 -07:00
|
|
|
ph := &packageHandle{
|
2019-11-20 14:15:00 -07:00
|
|
|
m: m,
|
2019-11-21 12:54:31 -07:00
|
|
|
goFiles: goFiles,
|
|
|
|
compiledGoFiles: compiledGoFiles,
|
2019-11-20 14:15:00 -07:00
|
|
|
mode: mode,
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
2019-10-29 10:16:15 -06:00
|
|
|
// Make sure all of the depList are sorted.
|
2019-10-31 16:06:25 -06:00
|
|
|
depList := append([]packageID{}, m.deps...)
|
2019-10-29 10:16:15 -06:00
|
|
|
sort.Slice(depList, func(i, j int) bool {
|
|
|
|
return depList[i] < depList[j]
|
2019-07-09 15:52:23 -06:00
|
|
|
})
|
2019-09-27 11:17:59 -06:00
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
deps := make(map[packagePath]*packageHandle)
|
2019-10-29 10:16:15 -06:00
|
|
|
|
2019-10-01 13:21:06 -06:00
|
|
|
// Begin computing the key by getting the depKeys for all dependencies.
|
2020-02-03 15:07:45 -07:00
|
|
|
var depKeys []packageHandleKey
|
2019-10-29 10:16:15 -06:00
|
|
|
for _, depID := range depList {
|
2020-02-07 12:38:36 -07:00
|
|
|
mode := source.ParseExported
|
|
|
|
if _, ok := s.isWorkspacePackage(depID); ok {
|
|
|
|
mode = source.ParseFull
|
|
|
|
}
|
|
|
|
depHandle, err := s.buildPackageHandle(ctx, depID, mode)
|
2019-10-01 13:21:06 -06:00
|
|
|
if err != nil {
|
2020-03-10 21:52:14 -06:00
|
|
|
event.Error(ctx, "no dep handle", err, tag.Package.Of(string(depID)))
|
2020-03-30 10:45:15 -06:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, nil, ctx.Err()
|
|
|
|
}
|
2019-10-16 17:09:37 -06:00
|
|
|
// One bad dependency should not prevent us from checking the entire package.
|
|
|
|
// Add a special key to mark a bad dependency.
|
2020-02-03 15:07:45 -07:00
|
|
|
depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
|
2019-10-16 17:09:37 -06:00
|
|
|
continue
|
2019-10-01 13:21:06 -06:00
|
|
|
}
|
2019-10-29 10:16:15 -06:00
|
|
|
deps[depHandle.m.pkgPath] = depHandle
|
2019-10-01 13:21:06 -06:00
|
|
|
depKeys = append(depKeys, depHandle.key)
|
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
ph.key = checkPackageKey(ctx, ph.m.id, ph.compiledGoFiles, m.config, depKeys)
|
2019-11-29 23:17:57 -07:00
|
|
|
return ph, deps, nil
|
2019-05-24 14:03:39 -06:00
|
|
|
}
|
|
|
|
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey) packageHandleKey {
|
2020-02-03 15:07:45 -07:00
|
|
|
var depBytes []byte
|
|
|
|
for _, dep := range deps {
|
|
|
|
depBytes = append(depBytes, []byte(dep)...)
|
|
|
|
}
|
|
|
|
return packageHandleKey(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(pghs), hashConfig(cfg), hashContents(depBytes)))))
|
2019-10-01 13:21:06 -06:00
|
|
|
}
|
|
|
|
|
2019-07-09 15:52:23 -06:00
|
|
|
// hashConfig returns the hash for the *packages.Config.
|
|
|
|
func hashConfig(config *packages.Config) string {
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
|
|
|
|
|
|
// Dir, Mode, Env, BuildFlags are the parts of the config that can change.
|
|
|
|
b.WriteString(config.Dir)
|
2020-05-06 20:30:17 -06:00
|
|
|
b.WriteString(string(rune(config.Mode)))
|
2019-07-09 15:52:23 -06:00
|
|
|
|
|
|
|
for _, e := range config.Env {
|
|
|
|
b.WriteString(e)
|
2019-03-11 15:14:55 -06:00
|
|
|
}
|
2019-07-09 15:52:23 -06:00
|
|
|
for _, f := range config.BuildFlags {
|
|
|
|
b.WriteString(f)
|
|
|
|
}
|
|
|
|
return hashContents(b.Bytes())
|
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) Check(ctx context.Context) (source.Package, error) {
|
|
|
|
return ph.check(ctx)
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) check(ctx context.Context) (*pkg, error) {
|
|
|
|
v := ph.handle.Get(ctx)
|
2019-07-09 15:52:23 -06:00
|
|
|
if v == nil {
|
2019-12-19 13:47:07 -07:00
|
|
|
return nil, ctx.Err()
|
2019-03-04 15:01:39 -07:00
|
|
|
}
|
2019-11-29 23:17:57 -07:00
|
|
|
data := v.(*packageData)
|
2019-07-09 15:52:23 -06:00
|
|
|
return data.pkg, data.err
|
|
|
|
}
|
2019-06-11 09:32:51 -06:00
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) CompiledGoFiles() []source.ParseGoHandle {
|
2020-04-07 20:54:42 -06:00
|
|
|
var files []source.ParseGoHandle
|
|
|
|
for _, f := range ph.compiledGoFiles {
|
|
|
|
files = append(files, f)
|
|
|
|
}
|
|
|
|
return files
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) ID() string {
|
|
|
|
return string(ph.m.id)
|
2019-09-10 15:33:35 -06:00
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) MissingDependencies() []string {
|
2019-09-23 18:19:50 -06:00
|
|
|
var md []string
|
2019-11-29 23:17:57 -07:00
|
|
|
for i := range ph.m.missingDeps {
|
2019-09-23 18:19:50 -06:00
|
|
|
md = append(md, string(i))
|
|
|
|
}
|
|
|
|
return md
|
|
|
|
}
|
|
|
|
|
2020-02-06 11:59:27 -07:00
|
|
|
func hashImports(ctx context.Context, wsPackages []source.PackageHandle) (string, error) {
|
|
|
|
results := make(map[string]bool)
|
|
|
|
var imports []string
|
|
|
|
for _, ph := range wsPackages {
|
|
|
|
// Check package since we do not always invalidate the metadata.
|
|
|
|
pkg, err := ph.Check(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
for _, path := range pkg.Imports() {
|
|
|
|
imp := path.PkgPath()
|
|
|
|
if _, ok := results[imp]; !ok {
|
|
|
|
results[imp] = true
|
|
|
|
imports = append(imports, imp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(imports)
|
|
|
|
hashed := strings.Join(imports, ",")
|
|
|
|
return hashContents([]byte(hashed)), nil
|
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) Cached() (source.Package, error) {
|
|
|
|
return ph.cached()
|
2019-09-19 10:39:03 -06:00
|
|
|
}
|
|
|
|
|
2019-11-29 23:17:57 -07:00
|
|
|
func (ph *packageHandle) cached() (*pkg, error) {
|
|
|
|
v := ph.handle.Cached()
|
2019-07-09 15:52:23 -06:00
|
|
|
if v == nil {
|
2019-11-29 23:17:57 -07:00
|
|
|
return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath)
|
2019-03-04 15:45:01 -07:00
|
|
|
}
|
2019-11-29 23:17:57 -07:00
|
|
|
data := v.(*packageData)
|
2019-07-09 15:52:23 -06:00
|
|
|
return data.pkg, data.err
|
|
|
|
}
|
2019-06-11 09:32:51 -06:00
|
|
|
|
2020-04-07 20:54:42 -06:00
|
|
|
func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]*parseGoHandle, error) {
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
pghs := make([]*parseGoHandle, 0, len(files))
|
2019-11-21 12:54:31 -07:00
|
|
|
for _, uri := range files {
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
fh, err := s.GetFile(ctx, uri)
|
2019-07-09 15:52:23 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
pghs = append(pghs, s.view.session.cache.parseGoHandle(ctx, fh, mode))
|
2019-07-09 15:52:23 -06:00
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
return pghs, nil
|
2019-03-04 15:01:39 -07:00
|
|
|
}
|
|
|
|
|
2020-04-07 20:54:42 -06:00
|
|
|
func typeCheck(ctx context.Context, fset *token.FileSet, m *metadata, mode source.ParseMode, goFiles, compiledGoFiles []*parseGoHandle, deps map[packagePath]*packageHandle) (*pkg, error) {
|
2020-04-20 10:14:12 -06:00
|
|
|
ctx, done := event.Start(ctx, "cache.importer.typeCheck", tag.Package.Of(string(m.id)))
|
2019-07-09 15:52:23 -06:00
|
|
|
defer done()
|
|
|
|
|
2019-10-20 17:57:03 -06:00
|
|
|
var rawErrors []error
|
2019-10-29 10:16:15 -06:00
|
|
|
for _, err := range m.errors {
|
2019-10-20 17:57:03 -06:00
|
|
|
rawErrors = append(rawErrors, err)
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:04:23 -06:00
|
|
|
pkg := &pkg{
|
2019-11-20 14:15:00 -07:00
|
|
|
id: m.id,
|
|
|
|
pkgPath: m.pkgPath,
|
|
|
|
mode: mode,
|
2019-11-21 12:54:31 -07:00
|
|
|
goFiles: goFiles,
|
|
|
|
compiledGoFiles: compiledGoFiles,
|
2020-02-11 08:12:40 -07:00
|
|
|
module: m.module,
|
2019-11-20 14:15:00 -07:00
|
|
|
imports: make(map[packagePath]*pkg),
|
|
|
|
typesSizes: m.typesSizes,
|
2019-03-06 14:33:47 -07:00
|
|
|
typesInfo: &types.Info{
|
2019-03-04 16:01:51 -07:00
|
|
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
|
|
|
Defs: make(map[*ast.Ident]types.Object),
|
|
|
|
Uses: make(map[*ast.Ident]types.Object),
|
|
|
|
Implicits: make(map[ast.Node]types.Object),
|
|
|
|
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
|
|
|
Scopes: make(map[ast.Node]*types.Scope),
|
|
|
|
},
|
2020-01-30 17:24:33 -07:00
|
|
|
forTest: m.forTest,
|
2019-03-04 16:01:51 -07:00
|
|
|
}
|
2019-07-09 15:52:23 -06:00
|
|
|
var (
|
2020-01-07 19:37:41 -07:00
|
|
|
files = make([]*ast.File, len(pkg.compiledGoFiles))
|
|
|
|
parseErrors = make([]error, len(pkg.compiledGoFiles))
|
|
|
|
actualErrors = make([]error, len(pkg.compiledGoFiles))
|
|
|
|
wg sync.WaitGroup
|
2020-04-07 20:54:42 -06:00
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
skipTypeErrors bool
|
2019-07-09 15:52:23 -06:00
|
|
|
)
|
2019-11-20 14:15:00 -07:00
|
|
|
for i, ph := range pkg.compiledGoFiles {
|
2019-06-21 15:00:02 -06:00
|
|
|
wg.Add(1)
|
2020-04-07 20:54:42 -06:00
|
|
|
go func(i int, ph *parseGoHandle) {
|
|
|
|
defer wg.Done()
|
|
|
|
data, err := ph.parse(ctx)
|
|
|
|
if err != nil {
|
|
|
|
actualErrors[i] = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
files[i], parseErrors[i], actualErrors[i] = data.ast, data.parseError, data.err
|
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
skipTypeErrors = skipTypeErrors || data.fixed
|
|
|
|
mu.Unlock()
|
2019-06-21 15:00:02 -06:00
|
|
|
}(i, ph)
|
|
|
|
}
|
2019-11-21 12:54:31 -07:00
|
|
|
for _, ph := range pkg.goFiles {
|
|
|
|
wg.Add(1)
|
|
|
|
// We need to parse the non-compiled go files, but we don't care about their errors.
|
|
|
|
go func(ph source.ParseGoHandle) {
|
|
|
|
ph.Parse(ctx)
|
|
|
|
wg.Done()
|
|
|
|
}(ph)
|
|
|
|
}
|
2019-06-21 15:00:02 -06:00
|
|
|
wg.Wait()
|
|
|
|
|
2019-10-20 17:57:03 -06:00
|
|
|
for _, e := range parseErrors {
|
|
|
|
if e != nil {
|
|
|
|
rawErrors = append(rawErrors, e)
|
2019-06-21 15:00:02 -06:00
|
|
|
}
|
2019-03-04 15:01:39 -07:00
|
|
|
}
|
2019-06-11 09:32:51 -06:00
|
|
|
|
2019-08-07 15:49:42 -06:00
|
|
|
var i int
|
|
|
|
for _, f := range files {
|
|
|
|
if f != nil {
|
|
|
|
files[i] = f
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
files = files[:i]
|
|
|
|
|
2019-06-11 12:50:43 -06:00
|
|
|
// Use the default type information for the unsafe package.
|
2019-10-20 17:57:03 -06:00
|
|
|
if pkg.pkgPath == "unsafe" {
|
2019-06-11 12:50:43 -06:00
|
|
|
pkg.types = types.Unsafe
|
2020-02-07 11:21:28 -07:00
|
|
|
// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
|
|
|
|
// race to Unsafe.completed.
|
|
|
|
return pkg, nil
|
2019-06-11 12:50:43 -06:00
|
|
|
} else if len(files) == 0 { // not the unsafe package, no parsed files
|
2020-01-14 11:59:17 -07:00
|
|
|
return nil, errors.Errorf("no parsed files for package %s, expected: %s, errors: %v, list errors: %v", pkg.pkgPath, pkg.compiledGoFiles, actualErrors, rawErrors)
|
2019-06-11 12:50:43 -06:00
|
|
|
} else {
|
2019-10-29 10:16:15 -06:00
|
|
|
pkg.types = types.NewPackage(string(m.pkgPath), m.name)
|
2019-04-22 14:04:44 -06:00
|
|
|
}
|
2019-03-11 15:14:55 -06:00
|
|
|
|
2019-03-04 15:01:39 -07:00
|
|
|
cfg := &types.Config{
|
2019-10-20 17:57:03 -06:00
|
|
|
Error: func(e error) {
|
2020-04-07 20:54:42 -06:00
|
|
|
// If we have fixed parse errors in any of the files,
|
|
|
|
// we should hide type errors, as they may be completely nonsensical.
|
|
|
|
if skipTypeErrors {
|
|
|
|
return
|
|
|
|
}
|
2019-10-20 17:57:03 -06:00
|
|
|
rawErrors = append(rawErrors, e)
|
2019-06-11 12:50:43 -06:00
|
|
|
},
|
2019-10-20 22:00:20 -06:00
|
|
|
Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
|
2020-01-31 11:15:27 -07:00
|
|
|
// If the context was cancelled, we should abort.
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2020-06-16 15:31:46 -06:00
|
|
|
dep := resolveImportPath(pkgPath, pkg, deps)
|
2019-10-20 22:00:20 -06:00
|
|
|
if dep == nil {
|
2019-10-29 10:16:15 -06:00
|
|
|
return nil, errors.Errorf("no package for import %s", pkgPath)
|
2019-10-20 22:00:20 -06:00
|
|
|
}
|
2020-02-19 14:01:45 -07:00
|
|
|
if !isValidImport(m.pkgPath, dep.m.pkgPath) {
|
2020-02-19 12:59:26 -07:00
|
|
|
return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
|
|
|
|
}
|
2019-10-20 22:00:20 -06:00
|
|
|
depPkg, err := dep.check(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pkg.imports[depPkg.pkgPath] = depPkg
|
|
|
|
return depPkg.types, nil
|
|
|
|
}),
|
2019-03-04 15:01:39 -07:00
|
|
|
}
|
2020-05-14 14:53:46 -06:00
|
|
|
// We want to type check cgo code if go/types supports it.
|
2020-06-10 15:46:51 -06:00
|
|
|
// We passed typecheckCgo to go/packages when we Loaded.
|
2020-06-10 14:05:41 -06:00
|
|
|
typesinternal.SetUsesCgo(cfg)
|
2020-05-14 14:53:46 -06:00
|
|
|
|
2019-10-29 10:16:15 -06:00
|
|
|
check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo)
|
2019-06-21 15:00:02 -06:00
|
|
|
|
2019-09-06 14:32:09 -06:00
|
|
|
// Type checking errors are handled via the config, so ignore them here.
|
|
|
|
_ = check.Files(files)
|
2019-11-11 17:28:40 -07:00
|
|
|
// If the context was cancelled, we may have returned a ton of transient
|
|
|
|
// errors to the type checker. Swallow them.
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2019-03-04 15:01:39 -07:00
|
|
|
|
2019-12-19 14:07:18 -07:00
|
|
|
// We don't care about a package's errors unless we have parsed it in full.
|
|
|
|
if mode == source.ParseFull {
|
|
|
|
for _, e := range rawErrors {
|
|
|
|
srcErr, err := sourceError(ctx, fset, pkg, e)
|
|
|
|
if err != nil {
|
2020-03-10 21:09:39 -06:00
|
|
|
event.Error(ctx, "unable to compute error positions", err, tag.Package.Of(pkg.ID()))
|
2019-12-19 14:07:18 -07:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
pkg.errors = append(pkg.errors, srcErr)
|
2020-03-10 08:14:56 -06:00
|
|
|
if err, ok := e.(types.Error); ok {
|
|
|
|
pkg.typeErrors = append(pkg.typeErrors, err)
|
|
|
|
}
|
2019-10-20 17:57:03 -06:00
|
|
|
}
|
|
|
|
}
|
2019-05-01 20:46:07 -06:00
|
|
|
return pkg, nil
|
|
|
|
}
|
|
|
|
|
2020-06-16 15:31:46 -06:00
|
|
|
// resolveImportPath resolves an import path in pkg to a package from deps.
|
|
|
|
// It should produce the same results as resolveImportPath:
|
|
|
|
// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/load/pkg.go;drc=641918ee09cb44d282a30ee8b66f99a0b63eaef9;l=990.
|
|
|
|
func resolveImportPath(importPath string, pkg *pkg, deps map[packagePath]*packageHandle) *packageHandle {
|
|
|
|
if dep := deps[packagePath(importPath)]; dep != nil {
|
|
|
|
return dep
|
|
|
|
}
|
|
|
|
// We may be in GOPATH mode, in which case we need to check vendor dirs.
|
|
|
|
searchDir := path.Dir(pkg.PkgPath())
|
|
|
|
for {
|
|
|
|
vdir := packagePath(path.Join(searchDir, "vendor", importPath))
|
|
|
|
if vdep := deps[vdir]; vdep != nil {
|
|
|
|
return vdep
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search until Dir doesn't take us anywhere new, e.g. "." or "/".
|
|
|
|
next := path.Dir(searchDir)
|
|
|
|
if searchDir == next {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
searchDir = next
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vendor didn't work. Let's try minimal module compatibility mode.
|
|
|
|
// In MMC, the packagePath is the canonical (.../vN/...) path, which
|
|
|
|
// is hard to calculate. But the go command has already resolved the ID
|
|
|
|
// to the non-versioned path, and we can take advantage of that.
|
|
|
|
for _, dep := range deps {
|
|
|
|
if dep.ID() == importPath {
|
|
|
|
return dep
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-19 14:01:45 -07:00
|
|
|
func isValidImport(pkgPath, importPkgPath packagePath) bool {
|
|
|
|
i := strings.LastIndex(string(importPkgPath), "/internal/")
|
|
|
|
if i == -1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if pkgPath == "command-line-arguments" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:00:20 -06:00
|
|
|
// An importFunc is an implementation of the single-method
|
|
|
|
// types.Importer interface based on a function value.
|
|
|
|
type importerFunc func(path string) (*types.Package, error)
|
|
|
|
|
|
|
|
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|