1
0
mirror of https://github.com/golang/go synced 2024-11-19 04:04:47 -07:00
go/internal/lsp/cache/load.go
Muir Manders 4bfb4c74ac internal/lsp: fix check for changed imports
We weren't comparing the right values when checking to see if a file's
import statements had changed. This was causing us to not refetch a
package's metadata when needed in certain cases. In particular, if you
typed out an import path by hand you would get stuck with "no metadata
for package" until you added or deleted another import line to trigger
metadata refresh.

Updates golang/go#32516, golang/go#32232

Change-Id: I1c99e9ee56d18f02c44c749d33679aa4e561c620
GitHub-Last-Rev: 20d46b30b520572b05e3228d9e43ba46dfd1be3d
GitHub-Pull-Request: golang/tools#112
Reviewed-on: https://go-review.googlesource.com/c/tools/+/181578
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
2019-06-11 15:01:45 +00:00

189 lines
5.1 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 pkg == nil || pkg.IsIllTyped() {
return nil, err
}
// 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
}