internal/lsp: call load in (*session).NewView

Add a source.Scope type that can be used to refer to directories or
files, and modify (*snapshot).load to take source.Scope.
Then call load in NewView.

Change-Id: I8f03c7b271d700b162100d2890d23219ef9578c2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/204822
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Michael Matloob 2019-11-01 16:45:32 -04:00
parent 8ceaad4c15
commit a4a09c7216
10 changed files with 106 additions and 28 deletions

View File

@ -39,7 +39,7 @@ func (s *snapshot) CheckPackageHandles(ctx context.Context, f source.File) ([]so
// We only need to this if it has been invalidated, and is therefore unvailable. // We only need to this if it has been invalidated, and is therefore unvailable.
if load { if load {
var err error var err error
m, err = s.load(ctx, f.URI()) m, err = s.load(ctx, source.FileURI(f.URI()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -61,7 +61,7 @@ func (s *snapshot) CheckPackageHandles(ctx context.Context, f source.File) ([]so
cphs = results cphs = results
} }
if len(cphs) == 0 { if len(cphs) == 0 {
return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI()) return nil, errors.Errorf("no CheckPackageHandles for %s", f)
} }
return cphs, nil return cphs, nil
} }
@ -86,7 +86,7 @@ func (s *snapshot) shouldCheck(fh source.FileHandle) (m []*metadata, cphs []sour
} }
// We expect to see a checked package for each package ID, // We expect to see a checked package for each package ID,
// and it should be parsed in full mode. // and it should be parsed in full mode.
cphs = s.getPackages(fh.Identity().URI, source.ParseFull) cphs = s.getPackages(source.FileURI(fh.Identity().URI), source.ParseFull)
if len(cphs) < len(m) { if len(cphs) < len(m) {
return m, nil, load, true return m, nil, load, true
} }

View File

@ -33,27 +33,43 @@ type metadata struct {
config *packages.Config config *packages.Config
} }
func (s *snapshot) load(ctx context.Context, uri span.URI) ([]*metadata, error) { 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)) ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
defer done() defer done()
cfg := s.view.Config(ctx) cfg := s.view.Config(ctx)
pkgs, err := packages.Load(cfg, fmt.Sprintf("file=%s", uri.Filename())) pkgs, err := packages.Load(cfg, query)
// If the context was canceled, return early. // If the context was canceled, return early.
// Otherwise, we might be type-checking an incomplete result. // Otherwise, we might be type-checking an incomplete result.
if err == context.Canceled { if err == context.Canceled {
return nil, errors.Errorf("no metadata for %s: %v", uri.Filename(), err) return nil, errors.Errorf("no metadata for %s: %v", uri, err)
} }
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs))) log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
if len(pkgs) == 0 { if len(pkgs) == 0 {
if err == nil { if err == nil {
err = errors.Errorf("go/packages.Load: no packages found for %s", uri) err = errors.Errorf("go/packages.Load: no packages found for %s", query)
} }
// Return this error as a diagnostic to the user. // Return this error as a diagnostic to the user.
return nil, err return nil, err
} }
m, prevMissingImports, err := s.updateMetadata(ctx, uri, pkgs, cfg) m, prevMissingImports, err := s.updateMetadata(ctx, scope, pkgs, cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -65,7 +81,7 @@ func (s *snapshot) load(ctx context.Context, uri span.URI) ([]*metadata, error)
} }
func validateMetadata(ctx context.Context, metadata []*metadata, prevMissingImports map[packageID]map[packagePath]struct{}) ([]*metadata, error) { 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 both rechecking it. // If we saw incorrect metadata for this package previously, don't bother rechecking it.
for _, m := range metadata { for _, m := range metadata {
if len(m.missingDeps) > 0 { if len(m.missingDeps) > 0 {
prev, ok := prevMissingImports[m.id] prev, ok := prevMissingImports[m.id]
@ -132,11 +148,22 @@ func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, current
return false return false
} }
func (s *snapshot) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, map[packageID]map[packagePath]struct{}, error) { 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. // 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{}) prevMissingImports := make(map[packageID]map[packagePath]struct{})
m := s.getMetadataForURI(uri)
for _, m := range m { for _, m := range m {
if len(m.missingDeps) > 0 { if len(m.missingDeps) > 0 {
prevMissingImports[m.id] = m.missingDeps prevMissingImports[m.id] = m.missingDeps

View File

@ -78,7 +78,7 @@ func (s *session) Cache() source.Cache {
return s.cache return s.cache
} }
func (s *session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) source.View { func (s *session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, error) {
index := atomic.AddInt64(&viewIndex, 1) index := atomic.AddInt64(&viewIndex, 1)
s.viewMu.Lock() s.viewMu.Lock()
defer s.viewMu.Unlock() defer s.viewMu.Unlock()
@ -118,11 +118,32 @@ func (s *session) NewView(ctx context.Context, name string, folder span.URI, opt
// so we immediately add builtin.go to the list of ignored files. // so we immediately add builtin.go to the list of ignored files.
v.buildBuiltinPackage(ctx) v.buildBuiltinPackage(ctx)
// Preemptively load everything in this directory.
// TODO(matloob): Determine if this can be done in parallel with something else.
// Perhaps different calls to NewView can be run in parallel?
// TODO(matloob): By default when a new file is opened, its data is invalidated
// and it's loaded again. Determine if the redundant reload can be avoided.
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock() // The code after the snapshot is used isn't expensive.
m, err := v.snapshot.load(ctx, source.DirectoryURI(folder))
if err != nil {
return nil, err
}
// Prepare CheckPackageHandles for every package that's been loaded.
// (*snapshot).CheckPackageHandle makes the assumption that every package that's
// been loaded has an existing checkPackageHandle.
for _, m := range m {
_, err := v.snapshot.checkPackageHandle(ctx, m.id, source.ParseFull)
if err != nil {
return nil, err
}
}
s.views = append(s.views, v) s.views = append(s.views, v)
// we always need to drop the view map // we always need to drop the view map
s.viewMap = make(map[span.URI]source.View) s.viewMap = make(map[span.URI]source.View)
debug.AddView(debugView{v}) debug.AddView(debugView{v})
return v return v, nil
} }
// View returns the view by name. // View returns the view by name.

View File

@ -79,11 +79,11 @@ func (s *snapshot) addPackage(cph *checkPackageHandle) {
s.packages[cph.packageKey()] = cph s.packages[cph.packageKey()] = cph
} }
func (s *snapshot) getPackages(uri span.URI, m source.ParseMode) (cphs []source.CheckPackageHandle) { func (s *snapshot) getPackages(uri source.FileURI, m source.ParseMode) (cphs []source.CheckPackageHandle) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if ids, ok := s.ids[uri]; ok { if ids, ok := s.ids[uri.URI()]; ok {
for _, id := range ids { for _, id := range ids {
key := packageKey{ key := packageKey{
id: id, id: id,
@ -178,6 +178,8 @@ func (s *snapshot) addAction(ah *actionHandle) {
} }
func (s *snapshot) getMetadataForURI(uri span.URI) (metadata []*metadata) { func (s *snapshot) getMetadataForURI(uri span.URI) (metadata []*metadata) {
// TODO(matloob): uri can be a file or directory. Should we update the mappings
// to map directories to their contained packages?
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -340,6 +342,9 @@ func (v *view) invalidateContent(ctx context.Context, f source.File, kind source
ids[id] = struct{}{} ids[id] = struct{}{}
} }
// Get the original FileHandle for the URI, if it exists.
originalFH := v.snapshot.getFile(f.URI())
switch changeType { switch changeType {
case protocol.Created: case protocol.Created:
// If this is a file we don't yet know about, // If this is a file we don't yet know about,
@ -358,6 +363,8 @@ func (v *view) invalidateContent(ctx context.Context, f source.File, kind source
} }
} }
} }
// If a file has been explicitly created, make sure that its original file handle is nil.
originalFH = nil
} }
if len(ids) == 0 { if len(ids) == 0 {
@ -369,9 +376,6 @@ func (v *view) invalidateContent(ctx context.Context, f source.File, kind source
v.snapshot.reverseDependencies(id, withoutTypes, map[packageID]struct{}{}) v.snapshot.reverseDependencies(id, withoutTypes, map[packageID]struct{}{})
} }
// Get the original FileHandle for the URI, if it exists.
originalFH := v.snapshot.getFile(f.URI())
// Make sure to clear out the content if there has been a deletion. // Make sure to clear out the content if there has been a deletion.
if changeType == protocol.Deleted { if changeType == protocol.Deleted {
v.session.clearOverlay(f.URI()) v.session.clearOverlay(f.URI())

View File

@ -167,7 +167,7 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
log.Print(ctx, buf.String()) log.Print(ctx, buf.String())
for _, folder := range s.pendingFolders { for _, folder := range s.pendingFolders {
if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil { if _, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
return err return err
} }
} }

View File

@ -53,7 +53,10 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
options := tests.DefaultOptions() options := tests.DefaultOptions()
session.SetOptions(options) session.SetOptions(options)
options.Env = data.Config.Env options.Env = data.Config.Env
session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options) _, err := session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
if err != nil {
t.Fatal(err)
}
for filename, content := range data.Config.Overlay { for filename, content := range data.Config.Overlay {
session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content) session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content)
} }

View File

@ -51,8 +51,12 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
session := cache.NewSession(ctx) session := cache.NewSession(ctx)
options := tests.DefaultOptions() options := tests.DefaultOptions()
options.Env = data.Config.Env options.Env = data.Config.Env
view, err := session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options)
if err != nil {
t.Fatal(err)
}
r := &runner{ r := &runner{
view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options), view: view,
data: data, data: data,
ctx: ctx, ctx: ctx,
} }

View File

@ -142,7 +142,7 @@ type Cache interface {
// A session may have many active views at any given time. // A session may have many active views at any given time.
type Session interface { type Session interface {
// NewView creates a new View and returns it. // NewView creates a new View and returns it.
NewView(ctx context.Context, name string, folder span.URI, options Options) View NewView(ctx context.Context, name string, folder span.URI, options Options) (View, error)
// Cache returns the cache that created this session. // Cache returns the cache that created this session.
Cache() Cache Cache() Cache
@ -287,6 +287,22 @@ type File interface {
Kind() FileKind Kind() FileKind
} }
type FileURI span.URI
func (f FileURI) URI() span.URI {
return span.URI(f)
}
type DirectoryURI span.URI
func (d DirectoryURI) URI() span.URI {
return span.URI(d)
}
type Scope interface {
URI() span.URI
}
// Package represents a Go package that has been type-checked. It maintains // Package represents a Go package that has been type-checked. It maintains
// only the relevant fields of a *go/packages.Package. // only the relevant fields of a *go/packages.Package.
type Package interface { type Package interface {

View File

@ -20,6 +20,7 @@ const (
RPCID = tag.Key("id") RPCID = tag.Key("id")
RPCDirection = tag.Key("direction") RPCDirection = tag.Key("direction")
File = tag.Key("file") File = tag.Key("file")
Directory = tag.Key("directory")
URI = tag.Key("URI") URI = tag.Key("URI")
Package = tag.Key("package") Package = tag.Key("package")
PackagePath = tag.Key("package_path") PackagePath = tag.Key("package_path")

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span" "golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors" errors "golang.org/x/xerrors"
) )
@ -23,23 +24,24 @@ func (s *Server) changeFolders(ctx context.Context, event protocol.WorkspaceFold
} }
for _, folder := range event.Added { for _, folder := range event.Added {
if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil { if _, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (s *Server) addView(ctx context.Context, name string, uri span.URI) error { func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source.View, error) {
s.stateMu.Lock() s.stateMu.Lock()
state := s.state state := s.state
s.stateMu.Unlock() s.stateMu.Unlock()
if state < serverInitialized { if state < serverInitialized {
return errors.Errorf("addView called before server initialized") return nil, errors.Errorf("addView called before server initialized")
} }
options := s.session.Options() options := s.session.Options()
s.fetchConfig(ctx, name, uri, &options) s.fetchConfig(ctx, name, uri, &options)
s.session.NewView(ctx, name, uri, options)
return nil return s.session.NewView(ctx, name, uri, options)
} }