1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:34:51 -07:00

internal/lsp: use the memoize package to get *token.Files

This change does not actually use the token handle for GetToken right
now, but implements the approach for memoizing *token.Files.

Change-Id: I75919f4e97abd6893b202c021adecd2c9dbfc2be
Reviewed-on: https://go-review.googlesource.com/c/tools/+/182277
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-06-13 15:55:53 -04:00
parent a82f0323a4
commit 61e0f78580
6 changed files with 139 additions and 32 deletions

View File

@ -157,41 +157,40 @@ func (imp *importer) typeCheck(id packageID) (*pkg, error) {
func (imp *importer) cachePackage(ctx context.Context, pkg *pkg, meta *metadata) { func (imp *importer) cachePackage(ctx context.Context, pkg *pkg, meta *metadata) {
for _, filename := range pkg.files { for _, filename := range pkg.files {
// TODO: If a file is in multiple packages, which package do we store? f, err := imp.view.getFile(span.FileURI(filename))
fURI := span.FileURI(filename)
f, err := imp.view.getFile(fURI)
if err != nil { if err != nil {
imp.view.Session().Logger().Errorf(ctx, "no file: %v", err) imp.view.session.log.Errorf(ctx, "no file: %v", err)
continue continue
} }
gof, ok := f.(*goFile) gof, ok := f.(*goFile)
if !ok { if !ok {
imp.view.Session().Logger().Errorf(ctx, "%v is not a Go file", f.URI()) imp.view.session.log.Errorf(ctx, "%v is not a Go file", f.URI())
continue continue
} }
// Set the package even if we failed to parse the file.
// Set the package even if we failed to parse the file otherwise
// future updates to this file won't refresh the package.
gof.pkg = pkg gof.pkg = pkg
fAST := pkg.syntax[filename] // Get the *token.File directly from the AST.
if fAST == nil { gof.ast = pkg.syntax[filename]
if gof.ast == nil {
imp.view.session.log.Errorf(ctx, "no AST information for %s", filename)
continue continue
} }
if gof.ast.file == nil {
if !fAST.file.Pos().IsValid() { imp.view.session.log.Errorf(ctx, "no AST for %s", filename)
imp.view.Session().Logger().Errorf(ctx, "invalid position for file %v", fAST.file.Name) }
pos := gof.ast.file.Pos()
if !pos.IsValid() {
imp.view.session.log.Errorf(ctx, "AST for %s has an invalid position", filename)
continue continue
} }
tok := imp.view.Session().Cache().FileSet().File(fAST.file.Pos()) tok := imp.view.session.cache.FileSet().File(pos)
if tok == nil { if tok == nil {
imp.view.Session().Logger().Errorf(ctx, "no token.File for %v", fAST.file.Name) imp.view.session.log.Errorf(ctx, "no *token.File for %s", filename)
continue continue
} }
gof.token = tok gof.token = tok
gof.ast = fAST gof.imports = gof.ast.file.Imports
gof.imports = fAST.file.Imports
} }
// Set imports of package to correspond to cached packages. // Set imports of package to correspond to cached packages.

View File

@ -29,10 +29,12 @@ type fileBase struct {
uris []span.URI uris []span.URI
fname string fname string
view *view view *view
handleMu sync.Mutex handleMu sync.Mutex
handle source.FileHandle handle source.FileHandle
token *token.File
token *token.File
} }
func basename(filename string) string { func basename(filename string) string {

View File

@ -112,7 +112,7 @@ func (v *view) parseImports(ctx context.Context, f *goFile) bool {
return true return true
} }
// Get file content in case we don't already have it. // Get file content in case we don't already have it.
parsed, _ := v.session.cache.ParseGo(f.Handle(ctx), source.ParseHeader).Parse(ctx) parsed, _ := v.session.cache.ParseGoHandle(f.Handle(ctx), source.ParseHeader).Parse(ctx)
if parsed == nil { if parsed == nil {
return true return true
} }

View File

@ -24,6 +24,7 @@ import (
// Limits the number of parallel parser calls per process. // Limits the number of parallel parser calls per process.
var parseLimit = make(chan bool, 20) var parseLimit = make(chan bool, 20)
// parseKey uniquely identifies a parsed Go file.
type parseKey struct { type parseKey struct {
file source.FileIdentity file source.FileIdentity
mode source.ParseMode mode source.ParseMode
@ -37,11 +38,12 @@ type parseGoHandle struct {
type parseGoData struct { type parseGoData struct {
memoize.NoCopy memoize.NoCopy
ast *ast.File ast *ast.File
err error err error
} }
func (c *cache) ParseGo(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle { func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle {
key := parseKey{ key := parseKey{
file: fh.Identity(), file: fh.Identity(),
mode: mode, mode: mode,
@ -104,7 +106,6 @@ func parseGo(ctx context.Context, c *cache, fh source.FileHandle, mode source.Pa
// //
// Because files are scanned in parallel, the token.Pos // Because files are scanned in parallel, the token.Pos
// positions of the resulting ast.Files are not ordered. // positions of the resulting ast.Files are not ordered.
//
func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) (map[string]*astFile, []error, error) { func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) (map[string]*astFile, []error, error) {
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
@ -124,7 +125,7 @@ func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) (map[
if ignoreFuncBodies { if ignoreFuncBodies {
mode = source.ParseExported mode = source.ParseExported
} }
ph := imp.view.session.cache.ParseGo(fh, mode) ph := imp.view.session.cache.ParseGoHandle(fh, mode)
// now read and parse in parallel // now read and parse in parallel
wg.Add(1) wg.Add(1)
go func(i int, filename string) { go func(i int, filename string) {

88
internal/lsp/cache/token.go vendored Normal file
View File

@ -0,0 +1,88 @@
package cache
import (
"context"
"fmt"
"go/token"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
)
type tokenKey struct {
file source.FileIdentity
}
type tokenHandle struct {
handle *memoize.Handle
file source.FileHandle
}
type tokenData struct {
memoize.NoCopy
tok *token.File
err error
}
func (c *cache) TokenHandle(fh source.FileHandle) source.TokenHandle {
key := tokenKey{
file: fh.Identity(),
}
h := c.store.Bind(key, func(ctx context.Context) interface{} {
data := &tokenData{}
data.tok, data.err = tokenFile(ctx, c, fh)
return data
})
return &tokenHandle{
handle: h,
}
}
func (h *tokenHandle) File() source.FileHandle {
return h.file
}
func (h *tokenHandle) Token(ctx context.Context) (*token.File, error) {
v := h.handle.Get(ctx)
if v == nil {
return nil, ctx.Err()
}
data := v.(*tokenData)
return data.tok, data.err
}
func tokenFile(ctx context.Context, c *cache, fh source.FileHandle) (*token.File, error) {
// First, check if we already have a parsed AST for this file's handle.
for _, mode := range []source.ParseMode{
source.ParseHeader,
source.ParseExported,
source.ParseFull,
} {
pk := parseKey{
file: fh.Identity(),
mode: mode,
}
pd, ok := c.store.Cached(pk).(*parseGoData)
if !ok {
continue
}
if pd.ast == nil {
continue
}
if !pd.ast.Pos().IsValid() {
continue
}
return c.FileSet().File(pd.ast.Pos()), nil
}
// We have not yet parsed this file.
buf, _, err := fh.Read(ctx)
if err != nil {
return nil, err
}
tok := c.FileSet().AddFile(fh.Identity().URI.Filename(), -1, len(buf))
if tok == nil {
return nil, fmt.Errorf("no token.File for %s", fh.Identity().URI)
}
return tok, nil
}

View File

@ -29,11 +29,12 @@ type FileIdentity struct {
type FileHandle interface { type FileHandle interface {
// FileSystem returns the file system this handle was acquired from. // FileSystem returns the file system this handle was acquired from.
FileSystem() FileSystem FileSystem() FileSystem
// Return the Identity for the file. // Return the Identity for the file.
Identity() FileIdentity Identity() FileIdentity
// Read reads the contents of a file and returns it along with its hash
// value. // Read reads the contents of a file and returns it along with its hash value.
// If the file is not available, retruns a nil slice and an error. // If the file is not available, returns a nil slice and an error.
Read(ctx context.Context) ([]byte, string, error) Read(ctx context.Context) ([]byte, string, error)
} }
@ -43,12 +44,23 @@ type FileSystem interface {
GetFile(uri span.URI) FileHandle GetFile(uri span.URI) FileHandle
} }
// ParseGoHandle represents a handle to the ast for a file. // TokenHandle represents a handle to the *token.File for a file.
type ParseGoHandle interface { type TokenHandle interface {
// File returns a file handle to get the ast for. // File returns a file handle for which to get the *token.File.
File() FileHandle File() FileHandle
// Token returns the *token.File for the file.
Token(ctx context.Context) (*token.File, error)
}
// ParseGoHandle represents a handle to the AST for a file.
type ParseGoHandle interface {
// File returns a file handle for which to get the AST.
File() FileHandle
// Mode returns the parse mode of this handle. // Mode returns the parse mode of this handle.
Mode() ParseMode Mode() ParseMode
// Parse returns the parsed AST for the file. // Parse returns the parsed AST for the file.
// If the file is not available, returns nil and an error. // If the file is not available, returns nil and an error.
Parse(ctx context.Context) (*ast.File, error) Parse(ctx context.Context) (*ast.File, error)
@ -61,11 +73,13 @@ const (
// ParseHeader specifies that the main package declaration and imports are needed. // ParseHeader specifies that the main package declaration and imports are needed.
// This is the mode used when attempting to examine the package graph structure. // This is the mode used when attempting to examine the package graph structure.
ParseHeader = ParseMode(iota) ParseHeader = ParseMode(iota)
// ParseExported specifies that the public symbols are needed, but things like // ParseExported specifies that the public symbols are needed, but things like
// private symbols and function bodies are not. // private symbols and function bodies are not.
// This mode is used for things where a package is being consumed only as a // This mode is used for things where a package is being consumed only as a
// dependency. // dependency.
ParseExported ParseExported
// ParseFull specifies the full AST is needed. // ParseFull specifies the full AST is needed.
// This is used for files of direct interest where the entire contents must // This is used for files of direct interest where the entire contents must
// be considered. // be considered.
@ -89,8 +103,11 @@ type Cache interface {
// FileSet returns the shared fileset used by all files in the system. // FileSet returns the shared fileset used by all files in the system.
FileSet() *token.FileSet FileSet() *token.FileSet
// Parse returns a ParseHandle for the given file handle. // Token returns a TokenHandle for the given file handle.
ParseGo(FileHandle, ParseMode) ParseGoHandle TokenHandle(FileHandle) TokenHandle
// ParseGo returns a ParseGoHandle for the given file handle.
ParseGoHandle(FileHandle, ParseMode) ParseGoHandle
} }
// Session represents a single connection from a client. // Session represents a single connection from a client.