internal/lsp: remove helpers for getting packages
We had too many options for functions to use to get type information for
a package. Now we stick with having one option to get the check package
handles, and then the caller can refine the results as needed.
Change-Id: I81f69a670e1539854ee23b6f364159a7de9b782f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/194457
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 2ccf62f..d724926 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -153,7 +153,7 @@
func (cph *checkPackageHandle) Cached(ctx context.Context) (source.Package, error) {
v := cph.handle.Cached()
if v == nil {
- return nil, errors.Errorf("no cached value for %s", cph.m.pkgPath)
+ return nil, errors.Errorf("no cached type information for %s", cph.m.pkgPath)
}
data := v.(*checkPackageData)
return data.pkg, data.err
@@ -345,10 +345,10 @@
defer gof.mu.Unlock()
// Set the package even if we failed to parse the file.
- if gof.pkgs == nil {
- gof.pkgs = make(map[packageID]source.CheckPackageHandle)
+ if gof.cphs == nil {
+ gof.cphs = make(map[packageID]source.CheckPackageHandle)
}
- gof.pkgs[cph.m.id] = cph
+ gof.cphs[cph.m.id] = cph
file, _, err := ph.Parse(ctx)
if file == nil {
diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go
index c8ffc1f..7bf6fd5 100644
--- a/internal/lsp/cache/gofile.go
+++ b/internal/lsp/cache/gofile.go
@@ -11,8 +11,6 @@
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/telemetry/log"
errors "golang.org/x/xerrors"
)
@@ -35,66 +33,11 @@
imports []*ast.ImportSpec
- pkgs map[packageID]source.CheckPackageHandle
+ cphs map[packageID]source.CheckPackageHandle
meta map[packageID]*metadata
}
-// metadata assumes that the caller holds the f.mu lock.
-func (f *goFile) metadata() []*metadata {
- result := make([]*metadata, 0, len(f.meta))
- for _, m := range f.meta {
- result = append(result, m)
- }
- return result
-}
-
-func (cache *cache) cachedAST(fh source.FileHandle, mode source.ParseMode) (*ast.File, error) {
- for _, m := range []source.ParseMode{
- source.ParseHeader,
- source.ParseExported,
- source.ParseFull,
- } {
- if m < mode {
- continue
- }
- if v, ok := cache.store.Cached(parseKey{
- file: fh.Identity(),
- mode: m,
- }).(*parseGoData); ok {
- return v.ast, v.err
- }
- }
- return nil, nil
-}
-
-func (f *goFile) GetPackages(ctx context.Context) ([]source.Package, error) {
- cphs, err := f.GetCheckPackageHandles(ctx)
- if err != nil {
- return nil, err
- }
- var pkgs []source.Package
- for _, cph := range cphs {
- pkg, err := cph.Check(ctx)
- if err != nil {
- log.Error(ctx, "failed to check package", err)
- }
- pkgs = append(pkgs, pkg)
- }
- if len(pkgs) == 0 {
- return nil, errors.Errorf("no packages for %s", f.URI())
- }
- return pkgs, nil
-}
-
-func (f *goFile) GetPackage(ctx context.Context) (source.Package, error) {
- cph, err := f.GetCheckPackageHandle(ctx)
- if err != nil {
- return nil, err
- }
- return cph.Check(ctx)
-}
-
-func (f *goFile) GetCheckPackageHandles(ctx context.Context) ([]source.CheckPackageHandle, error) {
+func (f *goFile) CheckPackageHandles(ctx context.Context) ([]source.CheckPackageHandle, error) {
ctx = telemetry.File.With(ctx, f.URI())
fh := f.Handle(ctx)
@@ -107,126 +50,16 @@
f.mu.Lock()
defer f.mu.Unlock()
- var cphs []source.CheckPackageHandle
- for _, cph := range f.pkgs {
- cphs = append(cphs, cph)
- }
- if len(cphs) == 0 {
+ if len(f.cphs) == 0 {
return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
}
+ var cphs []source.CheckPackageHandle
+ for _, cph := range f.cphs {
+ cphs = append(cphs, cph)
+ }
return cphs, nil
}
-func (f *goFile) GetCheckPackageHandle(ctx context.Context) (source.CheckPackageHandle, error) {
- cphs, err := f.GetCheckPackageHandles(ctx)
- if err != nil {
- return nil, err
- }
- return bestCheckPackageHandle(f.URI(), cphs)
-}
-
-func (f *goFile) GetCachedPackage(ctx context.Context) (source.Package, error) {
- f.mu.Lock()
- var cphs []source.CheckPackageHandle
- for _, cph := range f.pkgs {
- cphs = append(cphs, cph)
- }
- f.mu.Unlock()
-
- if len(cphs) == 0 {
- return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
- }
-
- cph, err := bestCheckPackageHandle(f.URI(), cphs)
- if err != nil {
- return nil, err
- }
- return cph.Cached(ctx)
-}
-
-func (f *goFile) GetCachedPackages(ctx context.Context) ([]source.Package, error) {
- f.mu.Lock()
- defer f.mu.Unlock()
-
- var pkgs []source.Package
- for _, cph := range f.pkgs {
- pkg, err := cph.Cached(ctx)
- if err != nil {
- return nil, err
- }
- pkgs = append(pkgs, pkg)
- }
- if len(pkgs) == 0 {
- return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
- }
- return pkgs, nil
-}
-
-// bestCheckPackageHandle picks the "narrowest" package for a given file.
-//
-// By "narrowest" package, we mean the package with the fewest number of files
-// that includes the given file. This solves the problem of test variants,
-// as the test will have more files than the non-test package.
-func bestCheckPackageHandle(uri span.URI, cphs []source.CheckPackageHandle) (source.CheckPackageHandle, error) {
- var result source.CheckPackageHandle
- for _, cph := range cphs {
- if result == nil || len(cph.Files()) < len(result.Files()) {
- result = cph
- }
- }
- if result == nil {
- return nil, errors.Errorf("no CheckPackageHandle for %s", uri)
- }
- return result, nil
-}
-
-func (f *goFile) wrongParseMode(ctx context.Context, fh source.FileHandle, mode source.ParseMode) bool {
- f.mu.Lock()
- defer f.mu.Unlock()
-
- for _, cph := range f.pkgs {
- for _, ph := range cph.Files() {
- if fh.Identity() == ph.File().Identity() {
- return ph.Mode() < mode
- }
- }
- }
- return true
-}
-
-// isDirty is true if the file needs to be type-checked.
-// It assumes that the file's view's mutex is held by the caller.
-func (f *goFile) isDirty(ctx context.Context, fh source.FileHandle) bool {
- f.mu.Lock()
- defer f.mu.Unlock()
-
- // If the the file has just been opened,
- // it may be part of more packages than we are aware of.
- //
- // Note: This must be the first case, otherwise we may not reset the value of f.justOpened.
- if f.justOpened {
- f.meta = make(map[packageID]*metadata)
- f.pkgs = make(map[packageID]source.CheckPackageHandle)
- f.justOpened = false
- return true
- }
- if len(f.meta) == 0 || len(f.pkgs) == 0 {
- return true
- }
- if len(f.missingImports) > 0 {
- return true
- }
- for _, cph := range f.pkgs {
- for _, file := range cph.Files() {
- // There is a type-checked package for the current file handle.
- if file.File().Identity() == fh.Identity() {
- return false
- }
- }
- }
- return true
-}
-
func (f *goFile) GetActiveReverseDeps(ctx context.Context) (files []source.GoFile) {
seen := make(map[packageID]struct{}) // visited packages
results := make(map[*goFile]struct{})
@@ -276,3 +109,59 @@
v.reverseDeps(ctx, seen, results, parentID)
}
}
+
+// metadata assumes that the caller holds the f.mu lock.
+func (f *goFile) metadata() []*metadata {
+ result := make([]*metadata, 0, len(f.meta))
+ for _, m := range f.meta {
+ result = append(result, m)
+ }
+ return result
+}
+
+func (f *goFile) wrongParseMode(ctx context.Context, fh source.FileHandle, mode source.ParseMode) bool {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ for _, cph := range f.cphs {
+ for _, ph := range cph.Files() {
+ if fh.Identity() == ph.File().Identity() {
+ return ph.Mode() < mode
+ }
+ }
+ }
+ return true
+}
+
+// isDirty is true if the file needs to be type-checked.
+// It assumes that the file's view's mutex is held by the caller.
+func (f *goFile) isDirty(ctx context.Context, fh source.FileHandle) bool {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ // If the the file has just been opened,
+ // it may be part of more packages than we are aware of.
+ //
+ // Note: This must be the first case, otherwise we may not reset the value of f.justOpened.
+ if f.justOpened {
+ f.meta = make(map[packageID]*metadata)
+ f.cphs = make(map[packageID]source.CheckPackageHandle)
+ f.justOpened = false
+ return true
+ }
+ if len(f.meta) == 0 || len(f.cphs) == 0 {
+ return true
+ }
+ if len(f.missingImports) > 0 {
+ return true
+ }
+ for _, cph := range f.cphs {
+ for _, file := range cph.Files() {
+ // There is a type-checked package for the current file handle.
+ if file.File().Identity() == fh.Identity() {
+ return false
+ }
+ }
+ }
+ return true
+}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 14b5879..d153af2 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -59,7 +59,7 @@
var toDelete []packageID
f.mu.Lock()
- for id, cph := range f.pkgs {
+ for id, cph := range f.cphs {
if cph != nil {
toDelete = append(toDelete, id)
}
@@ -124,8 +124,8 @@
for k := range f.meta {
delete(f.meta, k)
}
- for k := range f.pkgs {
- delete(f.pkgs, k)
+ for k := range f.cphs {
+ delete(f.cphs, k)
}
f.mu.Unlock()
@@ -160,7 +160,7 @@
// If we have already seen these missing imports before, and we have type information,
// there is no need to continue.
- if sameSet(missingImports, f.missingImports) && len(f.pkgs) != 0 {
+ if sameSet(missingImports, f.missingImports) && len(f.cphs) != 0 {
return nil, nil
}
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
index 8a0454f..ba7fed3 100644
--- a/internal/lsp/cache/pkg.go
+++ b/internal/lsp/cache/pkg.go
@@ -147,7 +147,7 @@
return string(pkg.pkgPath)
}
-func (pkg *pkg) GetHandles() []source.ParseGoHandle {
+func (pkg *pkg) Files() []source.ParseGoHandle {
return pkg.files
}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 0aa294b..b16e34e 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -330,7 +330,7 @@
var toDelete []packageID
f.mu.Lock()
- for id, cph := range f.pkgs {
+ for id, cph := range f.cphs {
if cph != nil {
toDelete = append(toDelete, id)
}
@@ -352,14 +352,14 @@
// package. This forces f's package's metadata to be reloaded next
// time the package is checked.
func (f *goFile) invalidateMeta(ctx context.Context) {
- pkgs, err := f.GetPackages(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
if err != nil {
log.Error(ctx, "invalidateMeta: GetPackages", err, telemetry.File.Of(f.URI()))
return
}
- for _, pkg := range pkgs {
- for _, pgh := range pkg.GetHandles() {
+ for _, pkg := range cphs {
+ for _, pgh := range pkg.Files() {
uri := pgh.File().Identity().URI
if gof, _ := f.view.FindFile(ctx, uri).(*goFile); gof != nil {
gof.mu.Lock()
@@ -402,7 +402,7 @@
continue
}
gof.mu.Lock()
- delete(gof.pkgs, id)
+ delete(gof.cphs, id)
gof.mu.Unlock()
}
return
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index fe87a1a..a8c7e8c 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -201,7 +201,12 @@
// TODO: This is technically racy because the diagnostics provided by the code action
// may not be the same as the ones that gopls is aware of.
// We need to figure out some way to solve this problem.
- pkg, err := gof.GetCachedPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, err
+ }
+ cph := source.NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Cached(ctx)
if err != nil {
return nil, err
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index e9281c0..17a8bc5 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -367,12 +367,17 @@
startTime := time.Now()
- pkg, err := f.GetPackage(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, nil, err
}
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
}
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index 162ca2b..561e5d8 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -136,7 +136,7 @@
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
return CompletionItem{}, errors.Errorf("no file for %s", obj.Name())
}
- ident, err := findIdentifier(c.ctx, c.view, []Package{pkg}, file, obj.Pos())
+ ident, err := findIdentifier(c.ctx, c.view, pkg, file, obj.Pos())
if err != nil {
return CompletionItem{}, err
}
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 79c2fb5..009d892 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -69,22 +69,11 @@
ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI()))
defer done()
- cphs, err := f.GetCheckPackageHandles(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
if err != nil {
return nil, err
}
- // Use the "biggest" package we know about.
- // If we know about a package and its in-package tests,
- // we should send diagnostics for both.
- var cph CheckPackageHandle
- for _, h := range cphs {
- if cph == nil || len(h.Files()) > len(cph.Files()) {
- cph = h
- }
- }
- if cph == nil {
- return nil, errors.Errorf("no package for file %s", f.URI())
- }
+ cph := NarrowestCheckPackageHandle(cphs)
pkg, err := cph.Check(ctx)
if err != nil {
log.Error(ctx, "no package for file", err)
@@ -92,7 +81,7 @@
}
// Prepare the reports we will send for the files in this package.
reports := make(map[span.URI][]Diagnostic)
- for _, fh := range pkg.GetHandles() {
+ for _, fh := range pkg.Files() {
clearReports(view, reports, fh.File().Identity().URI)
}
@@ -114,11 +103,16 @@
// Updates to the diagnostics for this package may need to be propagated.
revDeps := f.GetActiveReverseDeps(ctx)
for _, f := range revDeps {
- pkg, err := f.GetPackage(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
if err != nil {
return nil, err
}
- for _, fh := range pkg.GetHandles() {
+ cph := WidestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
+ if err != nil {
+ return nil, err
+ }
+ for _, fh := range pkg.Files() {
clearReports(view, reports, fh.File().Identity().URI)
}
diagnostics(ctx, view, pkg, reports)
@@ -193,7 +187,7 @@
m *protocol.ColumnMapper
err error
)
- for _, ph := range pkg.GetHandles() {
+ for _, ph := range pkg.Files() {
if ph.File().Identity().URI == spn.URI() {
fh = ph.File()
file, m, err = ph.Cached(ctx)
@@ -255,7 +249,12 @@
}
// If the package has changed since these diagnostics were computed,
// this may be incorrect. Should the package be associated with the diagnostic?
- pkg, err := gof.GetCachedPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
+ if err != nil {
+ return Diagnostic{}, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Cached(ctx)
if err != nil {
return Diagnostic{}, err
}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 3172224..b750b54 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -28,12 +28,17 @@
if !ok {
return nil, errors.Errorf("formatting is not supported for non-Go files")
}
- pkg, err := gof.GetPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
}
@@ -84,7 +89,13 @@
func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protocol.TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.Imports")
defer done()
- pkg, err := f.GetPackage(ctx)
+
+ cphs, err := f.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
@@ -92,7 +103,7 @@
return nil, errors.Errorf("%s has list errors, not running goimports", f.URI())
}
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
}
@@ -146,7 +157,12 @@
if !ok {
return nil, nil, errors.Errorf("no imports fixes for non-Go files: %v", err)
}
- pkg, err := gof.GetPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, nil, err
}
@@ -164,7 +180,7 @@
}
importFn := func(opts *imports.Options) error {
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
}
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 7f15a99..9088e9a 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -32,7 +32,7 @@
Declaration Declaration
- pkgs []Package
+ pkg Package
ident *ast.Ident
wasEmbeddedField bool
qf types.Qualifier
@@ -48,16 +48,17 @@
// Identifier returns identifier information for a position
// in a file, accounting for a potentially incomplete selector.
func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position) (*IdentifierInfo, error) {
- pkgs, err := f.GetPackages(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
if err != nil {
return nil, err
}
- pkg, err := bestPackage(f.URI(), pkgs)
+ cph := WidestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range cph.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
break
@@ -75,17 +76,17 @@
if err != nil {
return nil, err
}
- return findIdentifier(ctx, view, pkgs, file, rng.Start)
+ return findIdentifier(ctx, view, pkg, file, rng.Start)
}
-func findIdentifier(ctx context.Context, view View, pkgs []Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
- if result, err := identifier(ctx, view, pkgs, file, pos); err != nil || result != nil {
+func findIdentifier(ctx context.Context, view View, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
+ if result, err := identifier(ctx, view, pkg, file, pos); err != nil || result != nil {
return result, err
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
- ident, err := identifier(ctx, view, pkgs, file, pos-1)
+ ident, err := identifier(ctx, view, pkg, file, pos-1)
if ident == nil && err == nil {
err = errors.New("no identifier found")
}
@@ -93,14 +94,14 @@
}
// identifier checks a single position for a potential identifier.
-func identifier(ctx context.Context, view View, pkgs []Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
+func identifier(ctx context.Context, view View, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.identifier")
defer done()
var err error
// Handle import specs separately, as there is no formal position for a package declaration.
- if result, err := importSpec(ctx, view, file, pkgs, pos); result != nil || err != nil {
+ if result, err := importSpec(ctx, view, file, pkg, pos); result != nil || err != nil {
return result, err
}
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
@@ -108,12 +109,8 @@
return nil, errors.Errorf("can't find node enclosing position")
}
uri := span.FileURI(view.Session().Cache().FileSet().Position(pos).Filename)
- pkg, err := bestPackage(uri, pkgs)
- if err != nil {
- return nil, err
- }
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == uri {
ph = h
}
@@ -122,7 +119,7 @@
View: view,
File: ph,
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
- pkgs: pkgs,
+ pkg: pkg,
ident: searchForIdent(path[0]),
}
// No identifier at the given position.
@@ -279,9 +276,9 @@
}
// importSpec handles positions inside of an *ast.ImportSpec.
-func importSpec(ctx context.Context, view View, fAST *ast.File, pkgs []Package, pos token.Pos) (*IdentifierInfo, error) {
+func importSpec(ctx context.Context, view View, file *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
var imp *ast.ImportSpec
- for _, spec := range fAST.Imports {
+ for _, spec := range file.Imports {
if spec.Path.Pos() <= pos && pos < spec.Path.End() {
imp = spec
}
@@ -294,12 +291,8 @@
return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
uri := span.FileURI(view.Session().Cache().FileSet().Position(pos).Filename)
- pkg, err := bestPackage(uri, pkgs)
- if err != nil {
- return nil, err
- }
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == uri {
ph = h
}
@@ -308,7 +301,7 @@
View: view,
File: ph,
Name: importPath,
- pkgs: pkgs,
+ pkg: pkg,
}
if result.mappedRange, err = posToMappedRange(ctx, view, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
return nil, err
diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go
index 1d1a993..84c9d4d 100644
--- a/internal/lsp/source/references.go
+++ b/internal/lsp/source/references.go
@@ -28,64 +28,62 @@
func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.References")
defer done()
+
var references []*ReferenceInfo
// If the object declaration is nil, assume it is an import spec and do not look for references.
if i.Declaration.obj == nil {
return nil, errors.Errorf("no references for an import spec")
}
- for _, pkg := range i.pkgs {
- info := pkg.GetTypesInfo()
- if info == nil {
- return nil, errors.Errorf("package %s has no types info", pkg.PkgPath())
+ info := i.pkg.GetTypesInfo()
+ if info == nil {
+ return nil, errors.Errorf("package %s has no types info", i.pkg.PkgPath())
+ }
+ if i.Declaration.wasImplicit {
+ // The definition is implicit, so we must add it separately.
+ // This occurs when the variable is declared in a type switch statement
+ // or is an implicit package name. Both implicits are local to a file.
+ references = append(references, &ReferenceInfo{
+ Name: i.Declaration.obj.Name(),
+ mappedRange: i.Declaration.mappedRange,
+ obj: i.Declaration.obj,
+ pkg: i.pkg,
+ isDeclaration: true,
+ })
+ }
+ for ident, obj := range info.Defs {
+ if obj == nil || !sameObj(obj, i.Declaration.obj) {
+ continue
}
-
- if i.Declaration.wasImplicit {
- // The definition is implicit, so we must add it separately.
- // This occurs when the variable is declared in a type switch statement
- // or is an implicit package name. Both implicits are local to a file.
- references = append(references, &ReferenceInfo{
- Name: i.Declaration.obj.Name(),
- mappedRange: i.Declaration.mappedRange,
- obj: i.Declaration.obj,
- pkg: pkg,
- isDeclaration: true,
- })
+ rng, err := posToMappedRange(ctx, i.View, i.pkg, ident.Pos(), ident.End())
+ if err != nil {
+ return nil, err
}
- for ident, obj := range info.Defs {
- if obj == nil || !sameObj(obj, i.Declaration.obj) {
- continue
- }
- rng, err := posToMappedRange(ctx, i.View, pkg, ident.Pos(), ident.End())
- if err != nil {
- return nil, err
- }
- // Add the declarations at the beginning of the references list.
- references = append([]*ReferenceInfo{{
- Name: ident.Name,
- ident: ident,
- obj: obj,
- pkg: pkg,
- isDeclaration: true,
- mappedRange: rng,
- }}, references...)
+ // Add the declarations at the beginning of the references list.
+ references = append([]*ReferenceInfo{{
+ Name: ident.Name,
+ ident: ident,
+ obj: obj,
+ pkg: i.pkg,
+ isDeclaration: true,
+ mappedRange: rng,
+ }}, references...)
+ }
+ for ident, obj := range info.Uses {
+ if obj == nil || !sameObj(obj, i.Declaration.obj) {
+ continue
}
- for ident, obj := range info.Uses {
- if obj == nil || !sameObj(obj, i.Declaration.obj) {
- continue
- }
- rng, err := posToMappedRange(ctx, i.View, pkg, ident.Pos(), ident.End())
- if err != nil {
- return nil, err
- }
- references = append(references, &ReferenceInfo{
- Name: ident.Name,
- ident: ident,
- pkg: pkg,
- obj: obj,
- mappedRange: rng,
- })
+ rng, err := posToMappedRange(ctx, i.View, i.pkg, ident.Pos(), ident.End())
+ if err != nil {
+ return nil, err
}
+ references = append(references, &ReferenceInfo{
+ Name: ident.Name,
+ ident: ident,
+ pkg: i.pkg,
+ obj: obj,
+ mappedRange: rng,
+ })
}
return references, nil
}
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index 6f14547..0d5f3bb 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -102,15 +102,11 @@
if i.Declaration.obj.Parent() == types.Universe {
return nil, errors.Errorf("cannot rename builtin %q", i.Name)
}
- pkg, err := bestPackage(i.File.File().Identity().URI, i.pkgs)
- if err != nil {
- return nil, err
- }
- if pkg == nil || pkg.IsIllTyped() {
+ if i.pkg == nil || i.pkg.IsIllTyped() {
return nil, errors.Errorf("package for %s is ill typed", i.File.File().Identity().URI)
}
// Do not rename identifiers declared in another package.
- if pkg.GetTypes() != i.Declaration.obj.Pkg() {
+ if i.pkg.GetTypes() != i.Declaration.obj.Pkg() {
return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name())
}
@@ -184,11 +180,7 @@
file *ast.File
err error
)
- pkg, err := bestPackage(i.File.File().Identity().URI, i.pkgs)
- if err != nil {
- return nil, err
- }
- for _, ph := range pkg.GetHandles() {
+ for _, ph := range i.pkg.Files() {
if ph.File().Identity().URI == i.File.File().Identity().URI {
file, _, err = ph.Cached(ctx)
}
@@ -208,13 +200,13 @@
}
// Look for the object defined at NamePos.
- for _, obj := range pkg.GetTypesInfo().Defs {
+ for _, obj := range i.pkg.GetTypesInfo().Defs {
pkgName, ok := obj.(*types.PkgName)
if ok && pkgName.Pos() == namePos {
return getPkgNameIdentifier(ctx, i, pkgName)
}
}
- for _, obj := range pkg.GetTypesInfo().Implicits {
+ for _, obj := range i.pkg.GetTypesInfo().Implicits {
pkgName, ok := obj.(*types.PkgName)
if ok && pkgName.Pos() == namePos {
return getPkgNameIdentifier(ctx, i, pkgName)
@@ -230,14 +222,11 @@
obj: pkgName,
wasImplicit: true,
}
- pkg, err := bestPackage(ident.File.File().Identity().URI, ident.pkgs)
- if err != nil {
+ var err error
+ if decl.mappedRange, err = objToMappedRange(ctx, ident.View, ident.pkg, decl.obj); err != nil {
return nil, err
}
- if decl.mappedRange, err = objToMappedRange(ctx, ident.View, pkg, decl.obj); err != nil {
- return nil, err
- }
- if decl.node, err = objToNode(ctx, ident.View, pkg, decl.obj); err != nil {
+ if decl.node, err = objToNode(ctx, ident.View, ident.pkg, decl.obj); err != nil {
return nil, err
}
return &IdentifierInfo{
@@ -246,7 +235,7 @@
mappedRange: decl.mappedRange,
File: ident.File,
Declaration: decl,
- pkgs: ident.pkgs,
+ pkg: ident.pkg,
wasEmbeddedField: false,
qf: ident.qf,
}, nil
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 6260e50..4ede8cc 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -31,16 +31,17 @@
ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
defer done()
- pkgs, err := f.GetPackages(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
if err != nil {
return nil, err
}
- pkg, err := bestPackage(f.URI(), pkgs)
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
var ph ParseGoHandle
- for _, h := range pkg.GetHandles() {
+ for _, h := range pkg.Files() {
if h.File().Identity().URI == f.URI() {
ph = h
break
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index 42bfaa3..05bb6f4 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -18,7 +18,12 @@
ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols")
defer done()
- pkg, err := f.GetPackage(ctx)
+ cphs, err := f.CheckPackageHandles(ctx)
+ if err != nil {
+ return nil, err
+ }
+ cph := NarrowestCheckPackageHandle(cphs)
+ pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
@@ -26,7 +31,7 @@
file *ast.File
m *protocol.ColumnMapper
)
- for _, ph := range pkg.GetHandles() {
+ for _, ph := range pkg.Files() {
if ph.File().Identity().URI == f.URI() {
file, m, err = ph.Cached(ctx)
}
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index c992be0..5b563d6 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -51,22 +51,39 @@
return s.m.URI
}
-// bestPackage picks the "narrowest" package for a given file.
+// NarrowestCheckPackageHandle picks the "narrowest" package for a given file.
//
// By "narrowest" package, we mean the package with the fewest number of files
// that includes the given file. This solves the problem of test variants,
// as the test will have more files than the non-test package.
-func bestPackage(uri span.URI, pkgs []Package) (Package, error) {
- var result Package
- for _, pkg := range pkgs {
- if result == nil || len(pkg.GetHandles()) < len(result.GetHandles()) {
- result = pkg
+func NarrowestCheckPackageHandle(handles []CheckPackageHandle) CheckPackageHandle {
+ if len(handles) < 1 {
+ return nil
+ }
+ result := handles[0]
+ for _, handle := range handles[1:] {
+ if result == nil || len(handle.Files()) < len(result.Files()) {
+ result = handle
}
}
- if result == nil {
- return nil, errors.Errorf("no CheckPackageHandle for %s", uri)
+ return result
+}
+
+// WidestCheckPackageHandle returns the CheckPackageHandle containing the most files.
+//
+// This is useful for something like diagnostics, where we'd prefer to offer diagnostics
+// for as many files as possible.
+func WidestCheckPackageHandle(handles []CheckPackageHandle) CheckPackageHandle {
+ if len(handles) < 1 {
+ return nil
}
- return result, nil
+ result := handles[0]
+ for _, handle := range handles[1:] {
+ if result == nil || len(handle.Files()) > len(result.Files()) {
+ result = handle
+ }
+ }
+ return result
}
func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 6920312..0fbdf48 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -265,23 +265,9 @@
type GoFile interface {
File
- // GetCachedPackage returns the cached package for the file, if any.
- GetCachedPackage(ctx context.Context) (Package, error)
-
- // GetCachedPackage returns the cached package for the file, if any.
- GetCachedPackages(ctx context.Context) ([]Package, error)
-
- // GetPackage returns the CheckPackageHandle for the package that this file belongs to.
- GetCheckPackageHandle(ctx context.Context) (CheckPackageHandle, error)
-
- // GetPackages returns the CheckPackageHandles of the packages that this file belongs to.
- GetCheckPackageHandles(ctx context.Context) ([]CheckPackageHandle, error)
-
- // GetPackage returns the Package that this file belongs to.
- GetPackage(ctx context.Context) (Package, error)
-
- // GetPackages returns the Packages that this file belongs to.
- GetPackages(ctx context.Context) ([]Package, error)
+ // GetCheckPackageHandles returns the CheckPackageHandles for the packages
+ // that this file belongs to.
+ CheckPackageHandles(ctx context.Context) ([]CheckPackageHandle, error)
// GetActiveReverseDeps returns the active files belonging to the reverse
// dependencies of this file's package.
@@ -301,7 +287,7 @@
type Package interface {
ID() string
PkgPath() string
- GetHandles() []ParseGoHandle
+ Files() []ParseGoHandle
GetSyntax(context.Context) []*ast.File
GetErrors() []packages.Error
GetTypes() *types.Package
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 76ba2e8..96066ee 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -172,17 +172,21 @@
log.Error(ctx, "closing a non-Go file, no diagnostics to clear", nil, telemetry.File)
return nil
}
- pkg, err := gof.GetPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
if err != nil {
- return err
+ log.Error(ctx, "no CheckPackageHandles", err, telemetry.URI.Of(gof.URI()))
+ return nil
}
- for _, ph := range pkg.GetHandles() {
- // If other files from this package are open, don't clear.
- if s.session.IsOpen(ph.File().Identity().URI) {
- clear = nil
- return nil
+ for _, cph := range cphs {
+ for _, ph := range cph.Files() {
+ // If other files from this package are open, don't clear.
+ if s.session.IsOpen(ph.File().Identity().URI) {
+ clear = nil
+ return nil
+ }
+ clear = append(clear, ph.File().Identity().URI)
}
- clear = append(clear, ph.File().Identity().URI)
}
+
return nil
}
diff --git a/internal/lsp/watched_files.go b/internal/lsp/watched_files.go
index 98315e4..5740149 100644
--- a/internal/lsp/watched_files.go
+++ b/internal/lsp/watched_files.go
@@ -6,6 +6,7 @@
import (
"context"
+ "sort"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@@ -58,17 +59,22 @@
case protocol.Deleted:
log.Print(ctx, "watched file deleted", telemetry.File)
- pkg, err := gof.GetPackage(ctx)
+ cphs, err := gof.CheckPackageHandles(ctx)
if err != nil {
log.Error(ctx, "didChangeWatchedFiles: GetPackage", err, telemetry.File)
continue
}
-
- // Find a different file in the same package we can use to
- // trigger diagnostics.
+ // Find a different file in the same package we can use to trigger diagnostics.
+ // TODO(rstambler): Allow diagnostics to be called per-package to avoid this.
var otherFile source.GoFile
- for _, pgh := range pkg.GetHandles() {
- ident := pgh.File().Identity()
+ sort.Slice(cphs, func(i, j int) bool {
+ return len(cphs[i].Files()) > len(cphs[j].Files())
+ })
+ for _, ph := range cphs[0].Files() {
+ if len(cphs) > 1 && contains(cphs[1], ph.File()) {
+ continue
+ }
+ ident := ph.File().Identity()
if ident.URI == gof.URI() {
continue
}
@@ -77,7 +83,6 @@
break
}
}
-
s.session.DidChangeOutOfBand(ctx, gof, change.Type)
if otherFile != nil {
@@ -95,6 +100,14 @@
}
}
}
-
return nil
}
+
+func contains(cph source.CheckPackageHandle, fh source.FileHandle) bool {
+ for _, ph := range cph.Files() {
+ if ph.File().Identity().URI == fh.Identity().URI {
+ return true
+ }
+ }
+ return false
+}