1
0
mirror of https://github.com/golang/go synced 2024-09-30 22:48:32 -06:00

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>
This commit is contained in:
Rebecca Stambler 2019-09-09 19:26:26 -04:00
parent c52e9e2de4
commit 3b4f30a44f
19 changed files with 276 additions and 356 deletions

View File

@ -153,7 +153,7 @@ func (cph *checkPackageHandle) ID() string {
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 @@ func (imp *importer) cachePerFile(ctx context.Context, gof *goFile, ph source.Pa
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 {

View File

@ -11,8 +11,6 @@ import (
"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 @@ type goFile struct {
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 @@ func (f *goFile) GetCheckPackageHandles(ctx context.Context) ([]source.CheckPack
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 @@ func (v *view) reverseDeps(ctx context.Context, seen map[packageID]struct{}, res
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
}

View File

@ -59,7 +59,7 @@ func (view *view) load(ctx context.Context, f *goFile, fh source.FileHandle) ([]
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 @@ func (v *view) checkMetadata(ctx context.Context, f *goFile, fh source.FileHandl
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 @@ func validateMetadata(ctx context.Context, missingImports map[packagePath]struct
// 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
}

View File

@ -147,7 +147,7 @@ func (pkg *pkg) PkgPath() string {
return string(pkg.pkgPath)
}
func (pkg *pkg) GetHandles() []source.ParseGoHandle {
func (pkg *pkg) Files() []source.ParseGoHandle {
return pkg.files
}

View File

@ -330,7 +330,7 @@ func (f *goFile) invalidateContent(ctx context.Context) {
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 @@ func (f *goFile) invalidateContent(ctx context.Context) {
// 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 @@ func (v *view) remove(ctx context.Context, id packageID, seen map[packageID]stru
continue
}
gof.mu.Lock()
delete(gof.pkgs, id)
delete(gof.cphs, id)
gof.mu.Unlock()
}
return

View File

@ -201,7 +201,12 @@ func quickFixes(ctx context.Context, view source.View, gof source.GoFile) ([]pro
// 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
}

View File

@ -367,12 +367,17 @@ func Completion(ctx context.Context, view View, f GoFile, pos protocol.Position,
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
}

View File

@ -136,7 +136,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
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
}

View File

@ -69,22 +69,11 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
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 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
}
// 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 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
// 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 @@ func spanToRange(ctx context.Context, view View, pkg Package, spn span.Span, isT
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 @@ func toDiagnostic(ctx context.Context, view View, diag analysis.Diagnostic, cate
}
// 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
}

View File

@ -28,12 +28,17 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
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 formatSource(ctx context.Context, file File) ([]byte, error) {
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 @@ func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protoc
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 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
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 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
}
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
}

View File

@ -32,7 +32,7 @@ type IdentifierInfo struct {
Declaration Declaration
pkgs []Package
pkg Package
ident *ast.Ident
wasEmbeddedField bool
qf types.Qualifier
@ -48,16 +48,17 @@ type Declaration struct {
// 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 @@ func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position)
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 @@ func findIdentifier(ctx context.Context, view View, pkgs []Package, file *ast.Fi
}
// 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 @@ func identifier(ctx context.Context, view View, pkgs []Package, file *ast.File,
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 @@ func identifier(ctx context.Context, view View, pkgs []Package, file *ast.File,
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 @@ func objToNode(ctx context.Context, view View, pkg Package, obj types.Object) (a
}
// 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 @@ func importSpec(ctx context.Context, view View, fAST *ast.File, pkgs []Package,
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 @@ func importSpec(ctx context.Context, view View, fAST *ast.File, pkgs []Package,
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

View File

@ -28,64 +28,62 @@ type ReferenceInfo struct {
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
}

View File

@ -102,15 +102,11 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string)
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 @@ func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error
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 @@ func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error
}
// 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 @@ func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *t
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 @@ func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *t
mappedRange: decl.mappedRange,
File: ident.File,
Declaration: decl,
pkgs: ident.pkgs,
pkg: ident.pkg,
wasEmbeddedField: false,
qf: ident.qf,
}, nil

View File

@ -31,16 +31,17 @@ func SignatureHelp(ctx context.Context, view View, f GoFile, pos protocol.Positi
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

View File

@ -18,7 +18,12 @@ func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.Docum
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 @@ func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.Docum
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)
}

View File

@ -51,22 +51,39 @@ func (s mappedRange) URI() span.URI {
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 {

View File

@ -265,23 +265,9 @@ type File interface {
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 SumFile interface {
type Package interface {
ID() string
PkgPath() string
GetHandles() []ParseGoHandle
Files() []ParseGoHandle
GetSyntax(context.Context) []*ast.File
GetErrors() []packages.Error
GetTypes() *types.Package

View File

@ -172,17 +172,21 @@ func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocu
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
}

View File

@ -6,6 +6,7 @@ package lsp
import (
"context"
"sort"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -58,17 +59,22 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
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 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
break
}
}
s.session.DidChangeOutOfBand(ctx, gof, change.Type)
if otherFile != nil {
@ -95,6 +100,14 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
}
}
}
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
}