Skip to content

Commit a2de635

Browse files
committed
internal/lsp/cache: honor the go.work for computing workspace packages
When using Go workspaces, the go.work file should be used to determine which packages are workspace packages. For golang/go#48929 Change-Id: I1a8753ab7887daf193e093fca5070b4cc250a245 Reviewed-on: https://go-review.googlesource.com/c/tools/+/400822 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent cbb8e8e commit a2de635

File tree

9 files changed

+213
-46
lines changed

9 files changed

+213
-46
lines changed

gopls/internal/regtest/workspace/workspace_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -1305,3 +1305,67 @@ func (Server) Foo() {}
13051305
_, _ = env.GoToDefinition("other_test.go", env.RegexpSearch("other_test.go", "Server"))
13061306
})
13071307
}
1308+
1309+
// Test for golang/go#48929.
1310+
func TestClearNonWorkspaceDiagnostics(t *testing.T) {
1311+
testenv.NeedsGo1Point(t, 18) // uses go.work
1312+
1313+
const ws = `
1314+
-- go.work --
1315+
go 1.18
1316+
1317+
use (
1318+
./b
1319+
)
1320+
-- a/go.mod --
1321+
module a
1322+
1323+
go 1.17
1324+
-- a/main.go --
1325+
package main
1326+
1327+
func main() {
1328+
var V string
1329+
}
1330+
-- b/go.mod --
1331+
module b
1332+
1333+
go 1.17
1334+
-- b/main.go --
1335+
package b
1336+
1337+
import (
1338+
_ "fmt"
1339+
)
1340+
`
1341+
Run(t, ws, func(t *testing.T, env *Env) {
1342+
env.OpenFile("b/main.go")
1343+
env.Await(
1344+
OnceMet(
1345+
env.DoneWithOpen(),
1346+
NoDiagnostics("a/main.go"),
1347+
),
1348+
)
1349+
env.OpenFile("a/main.go")
1350+
env.Await(
1351+
OnceMet(
1352+
env.DoneWithOpen(),
1353+
env.DiagnosticAtRegexpWithMessage("a/main.go", "V", "declared but not used"),
1354+
),
1355+
)
1356+
env.CloseBuffer("a/main.go")
1357+
1358+
// Make an arbitrary edit because gopls explicitly diagnoses a/main.go
1359+
// whenever it is "changed".
1360+
//
1361+
// TODO(rfindley): it should not be necessary to make another edit here.
1362+
// Gopls should be smart enough to avoid diagnosing a.
1363+
env.RegexpReplace("b/main.go", "package b", "package b // a package")
1364+
env.Await(
1365+
OnceMet(
1366+
env.DoneWithChange(),
1367+
EmptyDiagnostics("a/main.go"),
1368+
),
1369+
)
1370+
})
1371+
}

internal/lsp/cache/load.go

+104-24
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
197197
}
198198
// TODO: once metadata is immutable, we shouldn't have to lock here.
199199
s.mu.Lock()
200-
err := s.computeMetadataUpdates(ctx, PackagePath(pkg.PkgPath), pkg, cfg, query, updates, nil)
200+
err := computeMetadataUpdates(ctx, s.meta, PackagePath(pkg.PkgPath), pkg, cfg, query, updates, nil)
201201
s.mu.Unlock()
202202
if err != nil {
203203
return err
@@ -216,7 +216,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
216216
delete(s.packages, key)
217217
}
218218
}
219-
s.workspacePackages = computeWorkspacePackages(s.meta)
219+
s.workspacePackages = computeWorkspacePackagesLocked(s, s.meta)
220220
s.dumpWorkspace("load")
221221
s.mu.Unlock()
222222

@@ -442,7 +442,7 @@ func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generati
442442
// computeMetadataUpdates populates the updates map with metadata updates to
443443
// apply, based on the given pkg. It recurs through pkg.Imports to ensure that
444444
// metadata exists for all dependencies.
445-
func (s *snapshot) computeMetadataUpdates(ctx context.Context, pkgPath PackagePath, pkg *packages.Package, cfg *packages.Config, query []string, updates map[PackageID]*KnownMetadata, path []PackageID) error {
445+
func computeMetadataUpdates(ctx context.Context, g *metadataGraph, pkgPath PackagePath, pkg *packages.Package, cfg *packages.Config, query []string, updates map[PackageID]*KnownMetadata, path []PackageID) error {
446446
id := PackageID(pkg.ID)
447447
if new := updates[id]; new != nil {
448448
return nil
@@ -494,28 +494,13 @@ func (s *snapshot) computeMetadataUpdates(ctx context.Context, pkgPath PackagePa
494494
m.Errors = append(m.Errors, err)
495495
}
496496

497-
uris := map[span.URI]struct{}{}
498497
for _, filename := range pkg.CompiledGoFiles {
499498
uri := span.URIFromPath(filename)
500499
m.CompiledGoFiles = append(m.CompiledGoFiles, uri)
501-
uris[uri] = struct{}{}
502500
}
503501
for _, filename := range pkg.GoFiles {
504502
uri := span.URIFromPath(filename)
505503
m.GoFiles = append(m.GoFiles, uri)
506-
uris[uri] = struct{}{}
507-
}
508-
509-
for uri := range uris {
510-
// In order for a package to be considered for the workspace, at least one
511-
// file must be contained in the workspace and not vendored.
512-
513-
// The package's files are in this view. It may be a workspace package.
514-
// Vendored packages are not likely to be interesting to the user.
515-
if !strings.Contains(string(uri), "/vendor/") && s.view.contains(uri) {
516-
m.HasWorkspaceFiles = true
517-
break
518-
}
519504
}
520505

521506
for importPath, importPkg := range pkg.Imports {
@@ -532,8 +517,8 @@ func (s *snapshot) computeMetadataUpdates(ctx context.Context, pkgPath PackagePa
532517
m.MissingDeps[importPkgPath] = struct{}{}
533518
continue
534519
}
535-
if s.noValidMetadataForIDLocked(importID) {
536-
if err := s.computeMetadataUpdates(ctx, importPkgPath, importPkg, cfg, query, updates, append(path, id)); err != nil {
520+
if noValidMetadataForID(g, importID) {
521+
if err := computeMetadataUpdates(ctx, g, importPkgPath, importPkg, cfg, query, updates, append(path, id)); err != nil {
537522
event.Error(ctx, "error in dependency", err)
538523
}
539524
}
@@ -542,12 +527,101 @@ func (s *snapshot) computeMetadataUpdates(ctx context.Context, pkgPath PackagePa
542527
return nil
543528
}
544529

545-
// computeWorkspacePackages computes workspace packages for the given metadata
546-
// graph.
547-
func computeWorkspacePackages(meta *metadataGraph) map[PackageID]PackagePath {
530+
// containsPackageLocked reports whether p is a workspace package for the
531+
// snapshot s.
532+
//
533+
// s.mu must be held while calling this function.
534+
func containsPackageLocked(s *snapshot, m *Metadata) bool {
535+
// In legacy workspace mode, or if a package does not have an associated
536+
// module, a package is considered inside the workspace if any of its files
537+
// are under the workspace root (and not excluded).
538+
//
539+
// Otherwise if the package has a module it must be an active module (as
540+
// defined by the module root or go.work file) and at least one file must not
541+
// be filtered out by directoryFilters.
542+
if m.Module != nil && s.workspace.moduleSource != legacyWorkspace {
543+
modURI := span.URIFromPath(m.Module.GoMod)
544+
_, ok := s.workspace.activeModFiles[modURI]
545+
if !ok {
546+
return false
547+
}
548+
549+
uris := map[span.URI]struct{}{}
550+
for _, uri := range m.CompiledGoFiles {
551+
uris[uri] = struct{}{}
552+
}
553+
for _, uri := range m.GoFiles {
554+
uris[uri] = struct{}{}
555+
}
556+
557+
for uri := range uris {
558+
// Don't use view.contains here. go.work files may include modules
559+
// outside of the workspace folder.
560+
if !strings.Contains(string(uri), "/vendor/") && !s.view.filters(uri) {
561+
return true
562+
}
563+
}
564+
return false
565+
}
566+
567+
return containsFileInWorkspaceLocked(s, m)
568+
}
569+
570+
// containsOpenFileLocked reports whether any file referenced by m is open in
571+
// the snapshot s.
572+
//
573+
// s.mu must be held while calling this function.
574+
func containsOpenFileLocked(s *snapshot, m *KnownMetadata) bool {
575+
uris := map[span.URI]struct{}{}
576+
for _, uri := range m.CompiledGoFiles {
577+
uris[uri] = struct{}{}
578+
}
579+
for _, uri := range m.GoFiles {
580+
uris[uri] = struct{}{}
581+
}
582+
583+
for uri := range uris {
584+
if s.isOpenLocked(uri) {
585+
return true
586+
}
587+
}
588+
return false
589+
}
590+
591+
// containsFileInWorkspace reports whether m contains any file inside the
592+
// workspace of the snapshot s.
593+
//
594+
// s.mu must be held while calling this function.
595+
func containsFileInWorkspaceLocked(s *snapshot, m *Metadata) bool {
596+
uris := map[span.URI]struct{}{}
597+
for _, uri := range m.CompiledGoFiles {
598+
uris[uri] = struct{}{}
599+
}
600+
for _, uri := range m.GoFiles {
601+
uris[uri] = struct{}{}
602+
}
603+
604+
for uri := range uris {
605+
// In order for a package to be considered for the workspace, at least one
606+
// file must be contained in the workspace and not vendored.
607+
608+
// The package's files are in this view. It may be a workspace package.
609+
// Vendored packages are not likely to be interesting to the user.
610+
if !strings.Contains(string(uri), "/vendor/") && s.view.contains(uri) {
611+
return true
612+
}
613+
}
614+
return false
615+
}
616+
617+
// computeWorkspacePackagesLocked computes workspace packages in the snapshot s
618+
// for the given metadata graph.
619+
//
620+
// s.mu must be held while calling this function.
621+
func computeWorkspacePackagesLocked(s *snapshot, meta *metadataGraph) map[PackageID]PackagePath {
548622
workspacePackages := make(map[PackageID]PackagePath)
549623
for _, m := range meta.metadata {
550-
if !m.HasWorkspaceFiles {
624+
if !containsPackageLocked(s, m.Metadata) {
551625
continue
552626
}
553627
if m.PkgFilesChanged {
@@ -567,6 +641,12 @@ func computeWorkspacePackages(meta *metadataGraph) map[PackageID]PackagePath {
567641
if allFilesHaveRealPackages(meta, m) {
568642
continue
569643
}
644+
645+
// We only care about command-line-arguments packages if they are still
646+
// open.
647+
if !containsOpenFileLocked(s, m) {
648+
continue
649+
}
570650
}
571651

572652
switch {

internal/lsp/cache/metadata.go

-7
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ type Metadata struct {
6767
// TODO(rfindley): this can probably just be a method, since it is derived
6868
// from other fields.
6969
IsIntermediateTestVariant bool
70-
71-
// HasWorkspaceFiles reports whether m contains any files that are considered
72-
// part of the workspace.
73-
//
74-
// TODO(golang/go#48929): this should be a property of the workspace
75-
// (the go.work file), not a constant.
76-
HasWorkspaceFiles bool
7770
}
7871

7972
// Name implements the source.Metadata interface.

internal/lsp/cache/session.go

+2
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ func bestViewForURI(uri span.URI, views []*View) *View {
323323
if longest != nil && len(longest.Folder()) > len(view.Folder()) {
324324
continue
325325
}
326+
// TODO(rfindley): this should consider the workspace layout (i.e.
327+
// go.work).
326328
if view.contains(uri) {
327329
longest = view
328330
}

internal/lsp/cache/snapshot.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,8 @@ func (s *snapshot) isActiveLocked(id PackageID, seen map[PackageID]bool) (active
768768
return true
769769
}
770770
}
771+
// TODO(rfindley): it looks incorrect that we don't also check GoFiles here.
772+
// If a CGo file is open, we want to consider the package active.
771773
for _, dep := range m.Deps {
772774
if s.isActiveLocked(dep, seen) {
773775
return true
@@ -1289,11 +1291,11 @@ func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
12891291
func (s *snapshot) noValidMetadataForID(id PackageID) bool {
12901292
s.mu.Lock()
12911293
defer s.mu.Unlock()
1292-
return s.noValidMetadataForIDLocked(id)
1294+
return noValidMetadataForID(s.meta, id)
12931295
}
12941296

1295-
func (s *snapshot) noValidMetadataForIDLocked(id PackageID) bool {
1296-
m := s.meta.metadata[id]
1297+
func noValidMetadataForID(g *metadataGraph, id PackageID) bool {
1298+
m := g.metadata[id]
12971299
return m == nil || !m.Valid
12981300
}
12991301

@@ -1789,8 +1791,10 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
17891791
}
17901792
}
17911793

1794+
// Compute invalidations based on file changes.
17921795
changedPkgFiles := map[PackageID]bool{} // packages whose file set may have changed
17931796
anyImportDeleted := false
1797+
anyFileOpenedOrClosed := false
17941798
for uri, change := range changes {
17951799
// Maybe reinitialize the view if we see a change in the vendor
17961800
// directory.
@@ -1800,6 +1804,10 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
18001804

18011805
// The original FileHandle for this URI is cached on the snapshot.
18021806
originalFH := s.files[uri]
1807+
var originalOpen, newOpen bool
1808+
_, originalOpen = originalFH.(*overlay)
1809+
_, newOpen = change.fileHandle.(*overlay)
1810+
anyFileOpenedOrClosed = originalOpen != newOpen
18031811

18041812
// If uri is a Go file, check if it has changed in a way that would
18051813
// invalidate metadata. Note that we can't use s.view.FileKind here,
@@ -1903,6 +1911,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
19031911
newGen.Inherit(v.handle)
19041912
result.packages[k] = v
19051913
}
1914+
19061915
// Copy the package analysis information.
19071916
for k, v := range s.actions {
19081917
if _, ok := idsToInvalidate[k.pkg.id]; ok {
@@ -1988,13 +1997,19 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
19881997
}
19891998
}
19901999

2000+
// Update metadata, if necessary.
19912001
if len(metadataUpdates) > 0 {
19922002
result.meta = s.meta.Clone(metadataUpdates)
1993-
result.workspacePackages = computeWorkspacePackages(result.meta)
19942003
} else {
19952004
// No metadata changes. Since metadata is only updated by cloning, it is
19962005
// safe to re-use the existing metadata here.
19972006
result.meta = s.meta
2007+
}
2008+
2009+
// Update workspace packages, if necessary.
2010+
if result.meta != s.meta || anyFileOpenedOrClosed {
2011+
result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
2012+
} else {
19982013
result.workspacePackages = s.workspacePackages
19992014
}
20002015

internal/lsp/cache/view.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -397,16 +397,27 @@ func (s *snapshot) locateTemplateFiles(ctx context.Context) {
397397
}
398398

399399
func (v *View) contains(uri span.URI) bool {
400+
// TODO(rfindley): should we ignore the root here? It is not provided by the
401+
// user, and is undefined when go.work is outside the workspace. It would be
402+
// better to explicitly consider the set of active modules wherever relevant.
400403
inRoot := source.InDir(v.rootURI.Filename(), uri.Filename())
401404
inFolder := source.InDir(v.folder.Filename(), uri.Filename())
405+
402406
if !inRoot && !inFolder {
403407
return false
404408
}
405-
// Filters are applied relative to the workspace folder.
406-
if inFolder {
407-
return !pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.rootURI.Filename(), v.gomodcache, v.Options())
409+
410+
return !v.filters(uri)
411+
}
412+
413+
// filters reports whether uri is filtered by the currently configured
414+
// directoryFilters.
415+
func (v *View) filters(uri span.URI) bool {
416+
// Only filter relative to the configured root directory.
417+
if source.InDirLex(v.folder.Filename(), uri.Filename()) {
418+
return pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.rootURI.Filename(), v.gomodcache, v.Options())
408419
}
409-
return true
420+
return false
410421
}
411422

412423
func (v *View) mapFile(uri span.URI, f *fileBase) {

internal/lsp/diagnostics.go

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func (d diagnosticSource) String() string {
6666
return "FromTypeChecking"
6767
case orphanedSource:
6868
return "FromOrphans"
69+
case workSource:
70+
return "FromGoWork"
6971
default:
7072
return fmt.Sprintf("From?%d?", d)
7173
}

0 commit comments

Comments
 (0)