1
0
mirror of https://github.com/golang/go synced 2024-11-06 13:46:16 -07:00
go/internal/lsp/cache/check.go

471 lines
13 KiB
Go
Raw Normal View History

// 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.
package cache
import (
"bytes"
"context"
"fmt"
"go/ast"
"go/types"
"path"
"sort"
"strings"
"sync"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/typesinternal"
errors "golang.org/x/xerrors"
)
type packageHandleKey string
type packageHandle struct {
handle *memoize.Handle
goFiles, compiledGoFiles []*parseGoHandle
// mode is the mode the the files were parsed in.
mode source.ParseMode
// m is the metadata associated with the package.
m *metadata
// key is the hashed key for the package.
key packageHandleKey
}
func (ph *packageHandle) packageKey() packageKey {
return packageKey{
id: ph.m.id,
mode: ph.mode,
}
}
// packageData contains the data produced by type-checking a package.
type packageData struct {
pkg *pkg
err error
}
// buildPackageHandle returns a packageHandle for a given package and mode.
func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
if ph := s.getPackage(id, mode); ph != nil {
return ph, nil
}
// Build the packageHandle for this ID and its dependencies.
ph, deps, err := s.buildKey(ctx, id, mode)
if err != nil {
return nil, err
}
// Do not close over the packageHandle or the snapshot in the Bind function.
// This creates a cycle, which causes the finalizers to never run on the handles.
// The possible cycles are:
//
// packageHandle.h.function -> packageHandle
// packageHandle.h.function -> snapshot -> packageHandle
//
m := ph.m
key := ph.key
internal/memoize: switch from GC-driven to explicit deletion The GC-based cache has given us a number of problems. First, memory leaks driven by reference cycles: the Go runtime cannot collect cycles involving finalizers, which prevents us from writing natural code in Bind callbacks. If we screw it up, we get a mysterious leak that takes a long time to track down. Second, the behavior is generally mysterious; it's hard to predict how long a value lasts, and harder to tell if a value being live is a bug. Third, we think that it may be interacting poorly with the GC, resulting in unnecessary memory usage. The structure of the values we put in the cache is not actually that complicated -- there are only 5 significant types: parse, typecheck, analyze, parse mod, and analyze mod. Managing them manually should not be conceptually difficult, and in fact we already do most of the work in (*snapshot).clone. In this CL the cache adds the concept of "generations", which function as reference counts on cache entries. Entries are still global and shared across generations, but will be explicitly deleted once no generations refer to them. The idea is that each snapshot is a new generation, and can inherit entries from the previous snapshot or leave them behind to be deleted. One obvious risk of this scheme is that we'll leave dangling references to values without actually inheriting them across generations. To prevent that, getting a value requires passing in the generation at which it's being read, and an error will be returned if that generation is dead. Change-Id: I4b30891efd7be4e10f2b84f4c067b0dee43dcf9c Reviewed-on: https://go-review.googlesource.com/c/tools/+/242838 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
2020-07-24 15:17:13 -06:00
h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
snapshot := arg.(*snapshot)
// Begin loading the direct dependencies, in parallel.
var wg sync.WaitGroup
for _, dep := range deps {
wg.Add(1)
go func(dep *packageHandle) {
dep.check(ctx, snapshot)
wg.Done()
}(dep)
}
data := &packageData{}
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
data.pkg, data.err = typeCheck(ctx, snapshot, m, mode, deps)
// Make sure that the workers above have finished before we return,
// especially in case of cancellation.
wg.Wait()
return data
})
ph.handle = h
// Cache the handle 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)
return ph, nil
}
// buildKey computes the key for a given packageHandle.
func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, map[packagePath]*packageHandle, error) {
m := s.getMetadata(id)
if m == nil {
return nil, nil, errors.Errorf("no metadata for %s", id)
}
goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode)
if err != nil {
return nil, nil, err
}
compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode)
if err != nil {
return nil, nil, err
}
ph := &packageHandle{
m: m,
goFiles: goFiles,
compiledGoFiles: compiledGoFiles,
mode: mode,
}
// Make sure all of the depList are sorted.
depList := append([]packageID{}, m.deps...)
sort.Slice(depList, func(i, j int) bool {
return depList[i] < depList[j]
})
deps := make(map[packagePath]*packageHandle)
// Begin computing the key by getting the depKeys for all dependencies.
var depKeys []packageHandleKey
for _, depID := range depList {
mode := source.ParseExported
if _, ok := s.isWorkspacePackage(depID); ok {
mode = source.ParseFull
}
depHandle, err := s.buildPackageHandle(ctx, depID, mode)
if err != nil {
event.Error(ctx, "no dep handle", err, tag.Package.Of(string(depID)))
if ctx.Err() != nil {
return nil, nil, ctx.Err()
}
// One bad dependency should not prevent us from checking the entire package.
// Add a special key to mark a bad dependency.
depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
continue
}
deps[depHandle.m.pkgPath] = depHandle
depKeys = append(depKeys, depHandle.key)
}
ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode)
return ph, deps, nil
}
func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode) packageHandleKey {
b := bytes.NewBuffer(nil)
b.WriteString(string(id))
b.WriteString(hashConfig(cfg))
b.WriteByte(byte(mode))
for _, dep := range deps {
b.WriteString(string(dep))
}
for _, cgf := range pghs {
b.WriteString(string(cgf.file.URI()))
b.WriteString(cgf.file.FileIdentity().Hash)
}
return packageHandleKey(hashContents(b.Bytes()))
}
// 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)
b.WriteString(string(rune(config.Mode)))
for _, e := range config.Env {
b.WriteString(e)
}
for _, f := range config.BuildFlags {
b.WriteString(f)
}
return hashContents(b.Bytes())
}
func (ph *packageHandle) Check(ctx context.Context, s source.Snapshot) (source.Package, error) {
return ph.check(ctx, s.(*snapshot))
}
func (ph *packageHandle) check(ctx context.Context, s *snapshot) (*pkg, error) {
internal/memoize: switch from GC-driven to explicit deletion The GC-based cache has given us a number of problems. First, memory leaks driven by reference cycles: the Go runtime cannot collect cycles involving finalizers, which prevents us from writing natural code in Bind callbacks. If we screw it up, we get a mysterious leak that takes a long time to track down. Second, the behavior is generally mysterious; it's hard to predict how long a value lasts, and harder to tell if a value being live is a bug. Third, we think that it may be interacting poorly with the GC, resulting in unnecessary memory usage. The structure of the values we put in the cache is not actually that complicated -- there are only 5 significant types: parse, typecheck, analyze, parse mod, and analyze mod. Managing them manually should not be conceptually difficult, and in fact we already do most of the work in (*snapshot).clone. In this CL the cache adds the concept of "generations", which function as reference counts on cache entries. Entries are still global and shared across generations, but will be explicitly deleted once no generations refer to them. The idea is that each snapshot is a new generation, and can inherit entries from the previous snapshot or leave them behind to be deleted. One obvious risk of this scheme is that we'll leave dangling references to values without actually inheriting them across generations. To prevent that, getting a value requires passing in the generation at which it's being read, and an error will be returned if that generation is dead. Change-Id: I4b30891efd7be4e10f2b84f4c067b0dee43dcf9c Reviewed-on: https://go-review.googlesource.com/c/tools/+/242838 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
2020-07-24 15:17:13 -06:00
v, err := ph.handle.Get(ctx, s.generation, s)
if err != nil {
return nil, err
}
data := v.(*packageData)
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)
}
internal/memoize: switch from GC-driven to explicit deletion The GC-based cache has given us a number of problems. First, memory leaks driven by reference cycles: the Go runtime cannot collect cycles involving finalizers, which prevents us from writing natural code in Bind callbacks. If we screw it up, we get a mysterious leak that takes a long time to track down. Second, the behavior is generally mysterious; it's hard to predict how long a value lasts, and harder to tell if a value being live is a bug. Third, we think that it may be interacting poorly with the GC, resulting in unnecessary memory usage. The structure of the values we put in the cache is not actually that complicated -- there are only 5 significant types: parse, typecheck, analyze, parse mod, and analyze mod. Managing them manually should not be conceptually difficult, and in fact we already do most of the work in (*snapshot).clone. In this CL the cache adds the concept of "generations", which function as reference counts on cache entries. Entries are still global and shared across generations, but will be explicitly deleted once no generations refer to them. The idea is that each snapshot is a new generation, and can inherit entries from the previous snapshot or leave them behind to be deleted. One obvious risk of this scheme is that we'll leave dangling references to values without actually inheriting them across generations. To prevent that, getting a value requires passing in the generation at which it's being read, and an error will be returned if that generation is dead. Change-Id: I4b30891efd7be4e10f2b84f4c067b0dee43dcf9c Reviewed-on: https://go-review.googlesource.com/c/tools/+/242838 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
2020-07-24 15:17:13 -06:00
func (ph *packageHandle) cached(g *memoize.Generation) (*pkg, error) {
v := ph.handle.Cached(g)
if v == nil {
return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath)
}
data := v.(*packageData)
return data.pkg, data.err
}
func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]*parseGoHandle, error) {
pghs := make([]*parseGoHandle, 0, len(files))
for _, uri := range files {
fh, err := s.GetFile(ctx, uri)
if err != nil {
return nil, err
}
pghs = append(pghs, s.parseGoHandle(ctx, fh, mode))
}
return pghs, nil
}
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source.ParseMode, deps map[packagePath]*packageHandle) (*pkg, error) {
ctx, done := event.Start(ctx, "cache.importer.typeCheck", tag.Package.Of(string(m.id)))
defer done()
var rawErrors []error
for _, err := range m.errors {
rawErrors = append(rawErrors, err)
}
fset := snapshot.view.session.cache.fset
pkg := &pkg{
m: m,
mode: mode,
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
goFiles: make([]*source.ParsedGoFile, len(m.goFiles)),
compiledGoFiles: make([]*source.ParsedGoFile, len(m.compiledGoFiles)),
module: m.module,
imports: make(map[packagePath]*pkg),
typesSizes: m.typesSizes,
typesInfo: &types.Info{
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),
},
}
var (
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
files = make([]*ast.File, len(m.compiledGoFiles))
parseErrors = make([]error, len(m.compiledGoFiles))
actualErrors = make([]error, len(m.compiledGoFiles))
wg sync.WaitGroup
mu sync.Mutex
skipTypeErrors bool
)
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
for i, cgf := range m.compiledGoFiles {
wg.Add(1)
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
go func(i int, cgf span.URI) {
defer wg.Done()
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
fh, err := snapshot.GetFile(ctx, cgf)
if err != nil {
actualErrors[i] = err
return
}
pgh := snapshot.parseGoHandle(ctx, fh, mode)
pgf, fixed, err := snapshot.parseGo(ctx, pgh)
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
if err != nil {
actualErrors[i] = err
return
}
pkg.compiledGoFiles[i] = pgf
files[i], parseErrors[i], actualErrors[i] = pgf.File, pgf.ParseErr, err
mu.Lock()
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
skipTypeErrors = skipTypeErrors || fixed
mu.Unlock()
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
}(i, cgf)
}
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
for i, gf := range m.goFiles {
wg.Add(1)
// We need to parse the non-compiled go files, but we don't care about their errors.
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
go func(i int, gf span.URI) {
defer wg.Done()
fh, err := snapshot.GetFile(ctx, gf)
if err != nil {
return
}
pgf, _ := snapshot.ParseGo(ctx, fh, mode)
pkg.goFiles[i] = pgf
}(i, gf)
}
wg.Wait()
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
for _, err := range actualErrors {
if err != nil {
return nil, err
}
}
for _, e := range parseErrors {
if e != nil {
rawErrors = append(rawErrors, e)
}
}
var i int
for _, f := range files {
if f != nil {
files[i] = f
i++
}
}
files = files[:i]
// Use the default type information for the unsafe package.
if pkg.m.pkgPath == "unsafe" {
pkg.types = types.Unsafe
// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
// race to Unsafe.completed.
return pkg, nil
} else if len(files) == 0 { // not the unsafe package, no parsed files
// Try to attach errors messages to the file as much as possible.
var found bool
for _, e := range rawErrors {
srcErr, err := sourceError(ctx, snapshot, pkg, e)
if err != nil {
continue
}
found = true
pkg.errors = append(pkg.errors, srcErr)
}
if found {
return pkg, nil
}
internal/lsp: replace ParseGoHandle with concrete data ParseGoHandles serve two purposes: they pin cache entries so that redundant calculations are cached, and they allow users to obtain the actual parsed AST. The former is an implementation detail, and the latter turns out to just be an annoyance. Parsed Go files are obtained from two places. By far the most common is from a type checked package. But a type checked package must by definition have already parsed all the files it contains, so the PGH is already computed and cannot have failed. Type checked packages can simply return the parsed file without requiring a separate Check operation. We do want to pin the cache entries in this case, which I've done by holding on to the PGH in cache.pkg. There are some cases where we directly parse a file, such as for the FoldingRange LSP call, which doesn't need type information. Those parses can actually fail, so we do need an error check. But we don't need the PGH; in all cases we are immediately using and discarding it. So it turns out we don't actually need the PGH type at all, at least not in the public API. Instead, we can pass around a concrete struct that has the various pieces of data directly available. This uncovered a bug in typeCheck: it should fail if it encounters any real errors. Change-Id: I203bf2dd79d5d65c01392d69c2cf4f7744fde7fc Reviewed-on: https://go-review.googlesource.com/c/tools/+/244021 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-07-21 13:15:06 -06:00
return nil, errors.Errorf("no parsed files for package %s, expected: %s, list errors: %v", pkg.m.pkgPath, pkg.compiledGoFiles, rawErrors)
} else {
pkg.types = types.NewPackage(string(m.pkgPath), string(m.name))
}
cfg := &types.Config{
Error: func(e error) {
// 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
}
rawErrors = append(rawErrors, e)
},
Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
// If the context was cancelled, we should abort.
if ctx.Err() != nil {
return nil, ctx.Err()
}
dep := resolveImportPath(pkgPath, pkg, deps)
if dep == nil {
return nil, errors.Errorf("no package for import %s", pkgPath)
}
if !isValidImport(m.pkgPath, dep.m.pkgPath) {
return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
}
depPkg, err := dep.check(ctx, snapshot)
if err != nil {
return nil, err
}
pkg.imports[depPkg.m.pkgPath] = depPkg
return depPkg.types, nil
}),
}
// We want to type check cgo code if go/types supports it.
// We passed typecheckCgo to go/packages when we Loaded.
typesinternal.SetUsesCgo(cfg)
check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo)
// Type checking errors are handled via the config, so ignore them here.
_ = check.Files(files)
// 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()
}
// 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, snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute error positions", err, tag.Package.Of(pkg.ID()))
continue
}
pkg.errors = append(pkg.errors, srcErr)
if err, ok := e.(types.Error); ok {
pkg.typeErrors = append(pkg.typeErrors, err)
}
}
}
return pkg, nil
}
// 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
}
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]))
}
// 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) }