1
0
mirror of https://github.com/golang/go synced 2024-10-01 09:28:37 -06:00
go/internal/lsp/cache/load.go
Heschi Kreinick ef6787d357 internal/lsp: track and parse non-compiled go files
When packages.Load'ing cgo packages, the authored files show up in
GoFiles, and the generated files show up in CompiledGoFiles. We need the
AST and type information for the latter, since they're the only thing we
can type check. But we also need the contents (and column mapper) for
the authored file so that we can navigate into it.

Store GoFiles in package metadata and checked Packages. Parse the extra
files, just for their mappers. Refactor the View functions a little bit,
since there's only one place that actually needs to find the mapper for
a file.

Updates golang/go#35720.

Change-Id: I9f96872a9a592bf0e11da27ebd8976c6db8752c9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/208502
Run-TryBot: Heschi Kreinick <heschi@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-25 19:20:50 +00:00

258 lines
7.3 KiB
Go

// 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 (
"context"
"fmt"
"go/types"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/tag"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
type metadata struct {
id packageID
pkgPath packagePath
name string
goFiles []span.URI
compiledGoFiles []span.URI
typesSizes types.Sizes
errors []packages.Error
deps []packageID
missingDeps map[packagePath]struct{}
// config is the *packages.Config associated with the loaded package.
config *packages.Config
}
var errNoPackagesFound = errors.New("no packages found for query")
func (s *snapshot) load(ctx context.Context, scope source.Scope) ([]*metadata, error) {
uri := scope.URI()
var query string
switch scope.(type) {
case source.FileURI:
query = fmt.Sprintf("file=%s", scope.URI().Filename())
case source.DirectoryURI:
query = fmt.Sprintf("%s/...", scope.URI().Filename())
// Simplify the query if it will be run in the requested directory.
// This ensures compatibility with Go 1.12 that doesn't allow
// <directory>/... in GOPATH mode.
if s.view.folder.Filename() == scope.URI().Filename() {
query = "./..."
}
default:
panic(fmt.Errorf("unsupported scope type %T", scope))
}
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
defer done()
cfg := s.view.Config(ctx)
pkgs, err := packages.Load(cfg, query)
// If the context was canceled, return early.
// Otherwise, we might be type-checking an incomplete result.
if err == context.Canceled {
return nil, errors.Errorf("no metadata for %s: %v", uri, err)
}
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
if len(pkgs) == 0 {
if err == nil {
err = errNoPackagesFound
}
}
if err != nil {
return nil, err
}
m, prevMissingImports, err := s.updateMetadata(ctx, scope, pkgs, cfg)
if err != nil {
return nil, err
}
meta, err := validateMetadata(ctx, m, prevMissingImports)
if err != nil {
return nil, err
}
return meta, nil
}
func validateMetadata(ctx context.Context, metadata []*metadata, prevMissingImports map[packageID]map[packagePath]struct{}) ([]*metadata, error) {
// If we saw incorrect metadata for this package previously, don't bother rechecking it.
for _, m := range metadata {
if len(m.missingDeps) > 0 {
prev, ok := prevMissingImports[m.id]
// There are missing imports that we previously hadn't seen before.
if !ok {
return metadata, nil
}
// The set of missing imports has changed.
if !sameSet(prev, m.missingDeps) {
return metadata, nil
}
} else {
// There are no missing imports.
return metadata, nil
}
}
return nil, nil
}
func sameSet(x, y map[packagePath]struct{}) bool {
if len(x) != len(y) {
return false
}
for k := range x {
if _, ok := y[k]; !ok {
return false
}
}
return true
}
// shouldLoad reparses a file's package and import declarations to
// determine if they have changed.
func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool {
if originalFH == nil {
return true
}
// Get the original and current parsed files in order to check package name and imports.
original, _, _, originalErr := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
current, _, _, currentErr := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
if originalErr != nil || currentErr != nil {
return (originalErr == nil) != (currentErr == nil)
}
// Check if the package's metadata has changed. The cases handled are:
//
// 1. A package's name has changed
// 2. A file's imports have changed
//
if original.Name.Name != current.Name.Name {
return true
}
// If the package's imports have changed, re-run `go list`.
if len(original.Imports) != len(current.Imports) {
return true
}
for i, importSpec := range original.Imports {
// TODO: Handle the case where the imports have just been re-ordered.
if importSpec.Path.Value != current.Imports[i].Path.Value {
return true
}
}
return false
}
func (s *snapshot) updateMetadata(ctx context.Context, uri source.Scope, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
// Clear metadata since we are re-running go/packages.
var m []*metadata
switch uri.(type) {
case source.FileURI:
m = s.getMetadataForURI(uri.URI())
case source.DirectoryURI:
for _, pkg := range pkgs {
if pkgMetadata := s.getMetadata(packageID(pkg.ID)); pkgMetadata != nil {
m = append(m, pkgMetadata)
}
}
default:
panic(fmt.Errorf("unsupported Scope type %T", uri))
}
prevMissingImports := make(map[packageID]map[packagePath]struct{})
for _, m := range m {
if len(m.missingDeps) > 0 {
prevMissingImports[m.id] = m.missingDeps
}
}
var results []*metadata
for _, pkg := range pkgs {
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
// Set the metadata for this package.
if err := s.updateImports(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}); err != nil {
return nil, nil, err
}
m := s.getMetadata(packageID(pkg.ID))
if m != nil {
results = append(results, m)
}
}
// Rebuild the import graph when the metadata is updated.
s.clearAndRebuildImportGraph()
if len(results) == 0 {
return nil, nil, errors.Errorf("no metadata for %s", uri)
}
return results, prevMissingImports, nil
}
func (s *snapshot) updateImports(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) error {
id := packageID(pkg.ID)
if _, ok := seen[id]; ok {
return errors.Errorf("import cycle detected: %q", id)
}
// Recreate the metadata rather than reusing it to avoid locking.
m := &metadata{
id: id,
pkgPath: pkgPath,
name: pkg.Name,
typesSizes: pkg.TypesSizes,
errors: pkg.Errors,
config: cfg,
}
for _, filename := range pkg.CompiledGoFiles {
uri := span.FileURI(filename)
m.compiledGoFiles = append(m.compiledGoFiles, uri)
s.addID(uri, m.id)
}
for _, filename := range pkg.GoFiles {
uri := span.FileURI(filename)
m.goFiles = append(m.goFiles, uri)
s.addID(uri, m.id)
}
seen[id] = struct{}{}
copied := make(map[packageID]struct{})
for k, v := range seen {
copied[k] = v
}
for importPath, importPkg := range pkg.Imports {
importPkgPath := packagePath(importPath)
importID := packageID(importPkg.ID)
m.deps = append(m.deps, importID)
// Don't remember any imports with significant errors.
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
if m.missingDeps == nil {
m.missingDeps = make(map[packagePath]struct{})
}
m.missingDeps[importPkgPath] = struct{}{}
continue
}
dep := s.getMetadata(importID)
if dep == nil {
if err := s.updateImports(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
log.Error(ctx, "error in dependency", err)
}
}
}
// Add the metadata to the cache.
s.setMetadata(m)
return nil
}