go/internal/lsp/cache/pkg.go
Rebecca Stambler b53505e708 internal/lsp: cache analysis using memoize package
This change moves to the approach of caching the analysis using the
memoize package. This means that we will do less work, as we no longer
need to recompute results that are unchanged. The cache key for an
analysis is simply the key of the CheckPackageHandle, along with the
name of the analyzer.

Change-Id: I0e589ccf088ff1de5670401b7207ffa77a254ceb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/200817
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-10-14 17:19:42 +00:00

162 lines
3.8 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"
"go/ast"
"go/types"
"sync"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
// pkg contains the type information needed by the source package.
type pkg struct {
view *view
// ID and package path have their own types to avoid being used interchangeably.
id packageID
pkgPath packagePath
files []source.ParseGoHandle
errors []packages.Error
imports map[packagePath]*pkg
types *types.Package
typesInfo *types.Info
typesSizes types.Sizes
diagMu sync.Mutex
diagnostics map[*analysis.Analyzer][]source.Diagnostic
}
// Declare explicit types for package paths and IDs to ensure that we never use
// an ID where a path belongs, and vice versa. If we confused the two, it would
// result in confusing errors because package IDs often look like package paths.
type packageID string
type packagePath string
func (p *pkg) ID() string {
return string(p.id)
}
func (p *pkg) PkgPath() string {
return string(p.pkgPath)
}
func (p *pkg) Files() []source.ParseGoHandle {
return p.files
}
func (p *pkg) File(uri span.URI) (source.ParseGoHandle, error) {
for _, ph := range p.Files() {
if ph.File().Identity().URI == uri {
return ph, nil
}
}
return nil, errors.Errorf("no ParseGoHandle for %s", uri)
}
func (p *pkg) GetSyntax(ctx context.Context) []*ast.File {
var syntax []*ast.File
for _, ph := range p.files {
file, _, _, err := ph.Cached(ctx)
if err == nil {
syntax = append(syntax, file)
}
}
return syntax
}
func (p *pkg) GetErrors() []packages.Error {
return p.errors
}
func (p *pkg) GetTypes() *types.Package {
return p.types
}
func (p *pkg) GetTypesInfo() *types.Info {
return p.typesInfo
}
func (p *pkg) GetTypesSizes() types.Sizes {
return p.typesSizes
}
func (p *pkg) IsIllTyped() bool {
return p.types == nil || p.typesInfo == nil || p.typesSizes == nil
}
func (p *pkg) GetImport(ctx context.Context, pkgPath string) (source.Package, error) {
if imp := p.imports[packagePath(pkgPath)]; imp != nil {
return imp, nil
}
// Don't return a nil pointer because that still satisfies the interface.
return nil, errors.Errorf("no imported package for %s", pkgPath)
}
func (p *pkg) SetDiagnostics(a *analysis.Analyzer, diags []source.Diagnostic) {
p.diagMu.Lock()
defer p.diagMu.Unlock()
if p.diagnostics == nil {
p.diagnostics = make(map[*analysis.Analyzer][]source.Diagnostic)
}
p.diagnostics[a] = diags
}
func (p *pkg) FindDiagnostic(pdiag protocol.Diagnostic) (*source.Diagnostic, error) {
p.diagMu.Lock()
defer p.diagMu.Unlock()
for a, diagnostics := range p.diagnostics {
if a.Name != pdiag.Source {
continue
}
for _, d := range diagnostics {
if d.Message != pdiag.Message {
continue
}
if protocol.CompareRange(d.Range, pdiag.Range) != 0 {
continue
}
return &d, nil
}
}
return nil, errors.Errorf("no matching diagnostic for %v", pdiag)
}
func (p *pkg) FindFile(ctx context.Context, uri span.URI) (source.ParseGoHandle, source.Package, error) {
// Special case for ignored files.
if p.view.Ignore(uri) {
return p.view.findIgnoredFile(ctx, uri)
}
queue := []*pkg{p}
seen := make(map[string]bool)
for len(queue) > 0 {
pkg := queue[0]
queue = queue[1:]
seen[pkg.ID()] = true
for _, ph := range pkg.files {
if ph.File().Identity().URI == uri {
return ph, pkg, nil
}
}
for _, dep := range pkg.imports {
if !seen[dep.ID()] {
queue = append(queue, dep)
}
}
}
return nil, nil, errors.Errorf("no file for %s", uri)
}