mirror of
https://github.com/golang/go
synced 2024-11-19 03:24:40 -07:00
1d0142ba47
If the context is canceled (or times out) during parsing, we were previously caching the package with no *ast.Files. Any further LSP queries against that package would fail because the package is already loaded, but none of the files are mapped to the package. Fix by propagating non-parse errors as "fatal" errors in parseFiles. typeCheck will propagate these errors and not cache the package. I also fixed the package cache to not cache errors loading packages. If you get an error like "context canceled" then none of the package's files are mapped to the package. This prevents the package from ever getting unmapped when its files are updated. I also added a retry mechanism where if the current request is not canceled but the package failed to load due to a previous request being canceled, this request can try loading the package again. Updates golang/go#32354, golang/go#32360 Change-Id: I466ddb8d336aeecf6e50f9f6d040787a86a60ca0 GitHub-Last-Rev: 5f1e7ef9c883b76a9c1b3636936d91ec0821d922 GitHub-Pull-Request: golang/tools#110 Reviewed-on: https://go-review.googlesource.com/c/tools/+/181317 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
192 lines
5.2 KiB
Go
192 lines
5.2 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/parser"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
func (v *view) loadParseTypecheck(ctx context.Context, f *goFile) ([]packages.Error, error) {
|
|
v.mcache.mu.Lock()
|
|
defer v.mcache.mu.Unlock()
|
|
|
|
// If the AST for this file is trimmed, and we are explicitly type-checking it,
|
|
// don't ignore function bodies.
|
|
if f.astIsTrimmed() {
|
|
f.invalidateAST()
|
|
}
|
|
|
|
// Save the metadata's current missing imports, if any.
|
|
var originalMissingImports map[string]struct{}
|
|
if f.meta != nil {
|
|
originalMissingImports = f.meta.missingImports
|
|
}
|
|
// Check if we need to run go/packages.Load for this file's package.
|
|
if errs, err := v.checkMetadata(ctx, f); err != nil {
|
|
return errs, err
|
|
}
|
|
// If `go list` failed to get data for the file in question (this should never happen).
|
|
if f.meta == nil {
|
|
return nil, fmt.Errorf("loadParseTypecheck: no metadata found for %v", f.filename())
|
|
}
|
|
// If we have already seen these missing imports before, and we still have type information,
|
|
// there is no need to continue.
|
|
if sameSet(originalMissingImports, f.meta.missingImports) && f.pkg != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
imp := &importer{
|
|
view: v,
|
|
seen: make(map[string]struct{}),
|
|
ctx: ctx,
|
|
fset: f.FileSet(),
|
|
topLevelPkgID: f.meta.id,
|
|
}
|
|
|
|
// Start prefetching direct imports.
|
|
for importPath := range f.meta.children {
|
|
go imp.Import(importPath)
|
|
}
|
|
// Type-check package.
|
|
pkg, err := imp.getPkg(f.meta.pkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if pkg == nil || pkg.IsIllTyped() {
|
|
return nil, fmt.Errorf("loadParseTypecheck: %s is ill typed", f.meta.pkgPath)
|
|
}
|
|
// If we still have not found the package for the file, something is wrong.
|
|
if f.pkg == nil {
|
|
return nil, fmt.Errorf("loadParseTypeCheck: no package found for %v", f.filename())
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func sameSet(x, y map[string]struct{}) bool {
|
|
if len(x) != len(y) {
|
|
return false
|
|
}
|
|
for k := range x {
|
|
if _, ok := y[k]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// checkMetadata determines if we should run go/packages.Load for this file.
|
|
// If yes, update the metadata for the file and its package.
|
|
func (v *view) checkMetadata(ctx context.Context, f *goFile) ([]packages.Error, error) {
|
|
if !v.parseImports(ctx, f) {
|
|
return nil, nil
|
|
}
|
|
pkgs, err := packages.Load(v.buildConfig(), fmt.Sprintf("file=%s", f.filename()))
|
|
if len(pkgs) == 0 {
|
|
if err == nil {
|
|
err = fmt.Errorf("no packages found for %s", f.filename())
|
|
}
|
|
// Return this error as a diagnostic to the user.
|
|
return []packages.Error{
|
|
{
|
|
Msg: err.Error(),
|
|
Kind: packages.ListError,
|
|
},
|
|
}, err
|
|
}
|
|
for _, pkg := range pkgs {
|
|
// If the package comes back with errors from `go list`,
|
|
// don't bother type-checking it.
|
|
if len(pkg.Errors) > 0 {
|
|
return pkg.Errors, fmt.Errorf("package %s has errors, skipping type-checking", pkg.PkgPath)
|
|
}
|
|
// Build the import graph for this package.
|
|
v.link(ctx, pkg.PkgPath, pkg, nil)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// reparseImports reparses a file's package and import declarations to
|
|
// determine if they have changed.
|
|
func (v *view) parseImports(ctx context.Context, f *goFile) bool {
|
|
if f.meta == nil || len(f.meta.missingImports) > 0 {
|
|
return true
|
|
}
|
|
// Get file content in case we don't already have it.
|
|
data, _, err := f.Handle(ctx).Read(ctx)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
parsed, _ := parser.ParseFile(f.FileSet(), f.filename(), data, parser.ImportsOnly)
|
|
if parsed == nil {
|
|
return true
|
|
}
|
|
|
|
// If the package name has changed, re-run `go list`.
|
|
if f.meta.name != parsed.Name.Name {
|
|
return true
|
|
}
|
|
// If the package's imports have changed, re-run `go list`.
|
|
if len(f.imports) != len(parsed.Imports) {
|
|
return true
|
|
}
|
|
for i, importSpec := range f.imports {
|
|
if importSpec.Path.Value != parsed.Imports[i].Path.Value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (v *view) link(ctx context.Context, pkgPath string, pkg *packages.Package, parent *metadata) *metadata {
|
|
m, ok := v.mcache.packages[pkgPath]
|
|
if !ok {
|
|
m = &metadata{
|
|
pkgPath: pkgPath,
|
|
id: pkg.ID,
|
|
typesSizes: pkg.TypesSizes,
|
|
parents: make(map[string]bool),
|
|
children: make(map[string]bool),
|
|
missingImports: make(map[string]struct{}),
|
|
}
|
|
v.mcache.packages[pkgPath] = m
|
|
}
|
|
// Reset any field that could have changed across calls to packages.Load.
|
|
m.name = pkg.Name
|
|
m.files = pkg.CompiledGoFiles
|
|
for _, filename := range m.files {
|
|
if f, _ := v.getFile(span.FileURI(filename)); f != nil {
|
|
if gof, ok := f.(*goFile); ok {
|
|
gof.meta = m
|
|
} else {
|
|
v.Session().Logger().Errorf(ctx, "not a Go file: %s", f.URI())
|
|
}
|
|
}
|
|
}
|
|
// Connect the import graph.
|
|
if parent != nil {
|
|
m.parents[parent.pkgPath] = true
|
|
parent.children[pkgPath] = true
|
|
}
|
|
for importPath, importPkg := range pkg.Imports {
|
|
if len(importPkg.Errors) > 0 {
|
|
m.missingImports[pkg.PkgPath] = struct{}{}
|
|
}
|
|
if _, ok := m.children[importPath]; !ok {
|
|
v.link(ctx, importPath, importPkg, m)
|
|
}
|
|
}
|
|
// Clear out any imports that have been removed.
|
|
for importPath := range m.children {
|
|
if _, ok := pkg.Imports[importPath]; !ok {
|
|
delete(m.children, importPath)
|
|
if child, ok := v.mcache.packages[importPath]; ok {
|
|
delete(child.parents, pkgPath)
|
|
}
|
|
}
|
|
}
|
|
return m
|
|
}
|