Skip to content

Commit e23f2f3

Browse files
committed
internal/lsp: reload metadata for orphaned files
go/packages overlay handling only really works for contains queries (file=), so our approach of reloading packages by package path (for workspace packages) wasn't handling newly created packages that need to be handled through overlays. Workaround this by reloading metadata for individual files that are missing it by running extra contains queries (only after the first metadata load for package paths). Be careful not to reload the same file multiple times if the first load did not succeed. Somewhat related, clear out `go list` errors in packages that go through overlay handling, since they will often be rendered irrelevant. I'm not sure if this is the right move, but if it's not, then we will have to do extra work to disregard those errors in gopls. Fixes golang/go#36661. Fixes golang/go#36635. Change-Id: Ib83cffcdf8a3e07da0f30e734d5e2c89691e1aba Reviewed-on: https://go-review.googlesource.com/c/tools/+/216141 Run-TryBot: Rebecca Stambler <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent 1b668f2 commit e23f2f3

File tree

6 files changed

+143
-41
lines changed

6 files changed

+143
-41
lines changed

go/packages/golist_overlay.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
132132
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
133133
modifiedPkgsSet[pkg.ID] = true
134134
}
135+
136+
// Clear out the package's errors, since we've probably corrected
137+
// them by adding the overlay. This may eliminate some legitimate
138+
// errors, but that's a risk with overlays in general.
139+
pkg.Errors = nil
140+
135141
imports, err := extractImports(opath, contents)
136142
if err != nil {
137143
// Let the parser or type checker report errors later.

go/packages/packages114_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"golang.org/x/tools/go/packages/packagestest"
1616
)
1717

18+
// These tests check fixes that are only available in Go 1.14.
19+
// They can be moved into packages_test.go when we no longer support 1.13.
20+
// See golang/go#35973 for more information.
1821
func TestInvalidFilesInOverlay(t *testing.T) { packagestest.TestAll(t, testInvalidFilesInOverlay) }
1922
func testInvalidFilesInOverlay(t *testing.T, exporter packagestest.Exporter) {
2023
exported := packagestest.Export(t, exporter, []packagestest.Module{
@@ -61,6 +64,19 @@ func testInvalidFilesInOverlay(t *testing.T, exporter packagestest.Exporter) {
6164
t.Fatal(err)
6265
}
6366
d := initial[0]
67+
var containsFile bool
68+
for _, goFile := range d.CompiledGoFiles {
69+
if f == goFile {
70+
containsFile = true
71+
break
72+
}
73+
}
74+
if !containsFile {
75+
t.Fatalf("expected %s in CompiledGoFiles, got %v", f, d.CompiledGoFiles)
76+
}
77+
if len(d.Errors) > 0 {
78+
t.Fatalf("expected no errors in package, got %v", d.Errors)
79+
}
6480
// Check value of d.D.
6581
dD := constant(d, "D")
6682
if dD == nil {

internal/lsp/cache/load.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,14 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) ([]*metadata
8181

8282
log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("query", query), tag.Of("packages", len(pkgs)))
8383
if len(pkgs) == 0 {
84-
if err == nil {
85-
err = errors.Errorf("no packages found for query %s", query)
86-
}
8784
return nil, err
8885
}
8986
return s.updateMetadata(ctx, scopes, pkgs, cfg)
9087
}
9188

9289
// shouldLoad reparses a file's package and import declarations to
93-
// determine if they have changed.
90+
// determine if the file requires a metadata reload.
9491
func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool {
95-
// TODO(rstambler): go.mod files should be tracked in the snapshot.
9692
if originalFH == nil {
9793
return currentFH.Identity().Kind == source.Go
9894
}
@@ -112,10 +108,8 @@ func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, current
112108
}
113109

114110
// Check if the package's metadata has changed. The cases handled are:
115-
//
116111
// 1. A package's name has changed
117112
// 2. A file's imports have changed
118-
//
119113
if original.Name.Name != current.Name.Name {
120114
return true
121115
}

internal/lsp/cache/session.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
9696
importedBy: make(map[packageID][]packageID),
9797
actions: make(map[actionKey]*actionHandle),
9898
workspacePackages: make(map[packageID]packagePath),
99+
unloadableFiles: make(map[span.URI]struct{}),
99100
},
100101
ignoredURIs: make(map[span.URI]struct{}),
101102
}

internal/lsp/cache/snapshot.go

Lines changed: 118 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type snapshot struct {
4949
// workspacePackages contains the workspace's packages, which are loaded
5050
// when the view is created.
5151
workspacePackages map[packageID]packagePath
52+
53+
// unloadableFiles keeps track of files that we've failed to load.
54+
unloadableFiles map[span.URI]struct{}
5255
}
5356

5457
type packageKey struct {
@@ -425,12 +428,17 @@ func (s *snapshot) addActionHandle(ah *actionHandle) {
425428
s.actions[key] = ah
426429
}
427430

428-
func (s *snapshot) getMetadataForURI(uri span.URI) (metadata []*metadata) {
429-
// TODO(matloob): uri can be a file or directory. Should we update the mappings
430-
// to map directories to their contained packages?
431+
func (s *snapshot) getMetadataForURI(uri span.URI) []*metadata {
431432
s.mu.Lock()
432433
defer s.mu.Unlock()
433434

435+
return s.getMetadataForURILocked(uri)
436+
}
437+
438+
func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) {
439+
// TODO(matloob): uri can be a file or directory. Should we update the mappings
440+
// to map directories to their contained packages?
441+
434442
for _, id := range s.ids[uri] {
435443
if m, ok := s.metadata[id]; ok {
436444
metadata = append(metadata, m)
@@ -489,13 +497,6 @@ func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
489497
return scope, ok
490498
}
491499

492-
func (s *snapshot) setWorkspacePackage(id packageID, pkgPath packagePath) {
493-
s.mu.Lock()
494-
defer s.mu.Unlock()
495-
496-
s.workspacePackages[id] = pkgPath
497-
}
498-
499500
func (s *snapshot) getFileURIs() []span.URI {
500501
s.mu.Lock()
501502
defer s.mu.Unlock()
@@ -545,32 +546,115 @@ func (s *snapshot) awaitLoaded(ctx context.Context) error {
545546

546547
// reloadWorkspace reloads the metadata for all invalidated workspace packages.
547548
func (s *snapshot) reloadWorkspace(ctx context.Context) error {
548-
scope := s.workspaceScope(ctx)
549-
if scope == nil {
550-
return nil
549+
// See which of the workspace packages are missing metadata.
550+
s.mu.Lock()
551+
var pkgPaths []interface{}
552+
for id, pkgPath := range s.workspacePackages {
553+
if s.metadata[id] == nil {
554+
pkgPaths = append(pkgPaths, pkgPath)
555+
}
556+
}
557+
s.mu.Unlock()
558+
559+
if len(pkgPaths) > 0 {
560+
if m, err := s.load(ctx, pkgPaths...); err == nil {
561+
for _, m := range m {
562+
s.setWorkspacePackage(ctx, m)
563+
}
564+
}
565+
}
566+
567+
// When we load ./... or a package path directly, we may not get packages
568+
// that exist only in overlays. As a workaround, we search all of the files
569+
// available in the snapshot and reload their metadata individually using a
570+
// file= query if the metadata is unavailable.
571+
if scopes := s.orphanedFileScopes(); len(scopes) > 0 {
572+
m, err := s.load(ctx, scopes...)
573+
574+
// If we failed to load some files, i.e. they have no metadata,
575+
// mark the failures so we don't bother retrying until the file's
576+
// content changes.
577+
//
578+
// TODO(rstambler): This may be an overestimate if the load stopped
579+
// early for an unrelated errors. Add a fallback?
580+
//
581+
// Check for context cancellation so that we don't incorrectly mark files
582+
// as unloadable, but don't return before setting all workspace packages.
583+
if ctx.Err() == nil && err != nil {
584+
s.mu.Lock()
585+
for _, scope := range scopes {
586+
uri := span.URI(scope.(fileURI))
587+
if s.getMetadataForURILocked(uri) == nil {
588+
s.unloadableFiles[uri] = struct{}{}
589+
}
590+
}
591+
s.mu.Unlock()
592+
}
593+
for _, m := range m {
594+
// If a package's files belong to this view, it is a workspace package
595+
// and should be added to the set of workspace packages.
596+
for _, uri := range m.compiledGoFiles {
597+
if !contains(s.view.session.viewsOf(uri), s.view) {
598+
continue
599+
}
600+
s.setWorkspacePackage(ctx, m)
601+
}
602+
}
551603
}
552-
_, err := s.load(ctx, scope)
553-
return err
604+
// Create package handles for all of the workspace packages.
605+
for _, id := range s.workspacePackageIDs() {
606+
if _, err := s.packageHandle(ctx, id); err != nil {
607+
return err
608+
}
609+
}
610+
return nil
554611
}
555612

556-
func (s *snapshot) workspaceScope(ctx context.Context) interface{} {
613+
func (s *snapshot) orphanedFileScopes() []interface{} {
557614
s.mu.Lock()
558615
defer s.mu.Unlock()
559616

560-
var pkgPaths []packagePath
561-
for id, pkgPath := range s.workspacePackages {
562-
if s.metadata[id] == nil {
563-
pkgPaths = append(pkgPaths, pkgPath)
617+
scopeSet := make(map[span.URI]struct{})
618+
for uri, fh := range s.files {
619+
// Don't try to reload metadata for go.mod files.
620+
if fh.Identity().Kind != source.Go {
621+
continue
622+
}
623+
// Don't reload metadata for files we've already deemed unloadable.
624+
if _, ok := s.unloadableFiles[uri]; ok {
625+
continue
626+
}
627+
if s.getMetadataForURILocked(uri) == nil {
628+
scopeSet[uri] = struct{}{}
564629
}
565630
}
566-
switch len(pkgPaths) {
567-
case 0:
568-
return nil
569-
case len(s.workspacePackages):
570-
return directoryURI(s.view.folder)
571-
default:
572-
return pkgPaths
631+
var scopes []interface{}
632+
for uri := range scopeSet {
633+
scopes = append(scopes, fileURI(uri))
573634
}
635+
return scopes
636+
}
637+
638+
func contains(views []*view, view *view) bool {
639+
for _, v := range views {
640+
if v == view {
641+
return true
642+
}
643+
}
644+
return false
645+
}
646+
647+
func (s *snapshot) setWorkspacePackage(ctx context.Context, m *metadata) {
648+
s.mu.Lock()
649+
defer s.mu.Unlock()
650+
651+
// A test variant of a package can only be loaded directly by loading
652+
// the non-test variant with -test. Track the import path of the non-test variant.
653+
pkgPath := m.pkgPath
654+
if m.forTest != "" {
655+
pkgPath = m.forTest
656+
}
657+
s.workspacePackages[m.id] = pkgPath
574658
}
575659

576660
func (s *snapshot) clone(ctx context.Context, withoutURIs []span.URI) *snapshot {
@@ -587,12 +671,17 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs []span.URI) *snapshot
587671
actions: make(map[actionKey]*actionHandle),
588672
files: make(map[span.URI]source.FileHandle),
589673
workspacePackages: make(map[packageID]packagePath),
674+
unloadableFiles: make(map[span.URI]struct{}),
590675
}
591676

592677
// Copy all of the FileHandles.
593678
for k, v := range s.files {
594679
result.files[k] = v
595680
}
681+
// Copy the set of unloadable files.
682+
for k, v := range s.unloadableFiles {
683+
result.unloadableFiles[k] = v
684+
}
596685

597686
// transitiveIDs keeps track of transitive reverse dependencies.
598687
// If an ID is present in the map, invalidate its types.
@@ -664,6 +753,8 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs []span.URI) *snapshot
664753
} else {
665754
result.files[withoutURI] = currentFH
666755
}
756+
// Make sure to remove the changed file from the unloadable set.
757+
delete(result.unloadableFiles, withoutURI)
667758
}
668759

669760
// Collect the IDs for the packages associated with the excluded URIs.

internal/lsp/cache/view.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -557,13 +557,7 @@ func (v *view) initialize(ctx context.Context, s *snapshot) {
557557
}
558558
continue
559559
}
560-
// A test variant of a package can only be loaded directly by loading
561-
// the non-test variant with -test. Track the import path of the non-test variant.
562-
pkgPath := m.pkgPath
563-
if m.forTest != "" {
564-
pkgPath = m.forTest
565-
}
566-
s.setWorkspacePackage(m.id, pkgPath)
560+
s.setWorkspacePackage(ctx, m)
567561
if _, err := s.packageHandle(ctx, m.id); err != nil {
568562
return err
569563
}

0 commit comments

Comments
 (0)