mirror of
https://github.com/golang/go
synced 2024-11-18 17:54:57 -07:00
internal/lsp/source: add support for references in the same workspace
When looking for references, look in the entire workspace rather than the same package. This makes the references query more expensive because it needs to look at every package in the workspace, but hopefully it shouln't be user-noticable. This can be made more efficient by only checking packages that are transitive reverse dependencies. I don't think a mechanism to get all transitive reverse dependencies exists yet. One of the references test have been changed: it looked up references of the builtin int type, but now there are so many refererences that the test too slow and doesn't make sense any more. Instead look up references of the type "i" in that file. Change-Id: I93b3bd3795386f06ce488e76e6c7c8c1b1074e22 Reviewed-on: https://go-review.googlesource.com/c/tools/+/206883 Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
a764947cb5
commit
caa0b0f7d5
21
internal/lsp/cache/session.go
vendored
21
internal/lsp/cache/session.go
vendored
@ -110,12 +110,13 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
filesByURI: make(map[span.URI]viewFile),
|
filesByURI: make(map[span.URI]viewFile),
|
||||||
filesByBase: make(map[string][]viewFile),
|
filesByBase: make(map[string][]viewFile),
|
||||||
snapshot: &snapshot{
|
snapshot: &snapshot{
|
||||||
packages: make(map[packageKey]*checkPackageHandle),
|
packages: make(map[packageKey]*checkPackageHandle),
|
||||||
ids: make(map[span.URI][]packageID),
|
ids: make(map[span.URI][]packageID),
|
||||||
metadata: make(map[packageID]*metadata),
|
metadata: make(map[packageID]*metadata),
|
||||||
files: make(map[span.URI]source.FileHandle),
|
files: make(map[span.URI]source.FileHandle),
|
||||||
importedBy: make(map[packageID][]packageID),
|
importedBy: make(map[packageID][]packageID),
|
||||||
actions: make(map[actionKey]*actionHandle),
|
actions: make(map[actionKey]*actionHandle),
|
||||||
|
workspacePackages: make(map[packageID]bool),
|
||||||
},
|
},
|
||||||
ignoredURIs: make(map[span.URI]struct{}),
|
ignoredURIs: make(map[span.URI]struct{}),
|
||||||
builtin: &builtinPkg{},
|
builtin: &builtinPkg{},
|
||||||
@ -146,12 +147,10 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
// Prepare CheckPackageHandles for every package that's been loaded.
|
// Prepare CheckPackageHandles for every package that's been loaded.
|
||||||
// (*snapshot).CheckPackageHandle makes the assumption that every package that's
|
// (*snapshot).CheckPackageHandle makes the assumption that every package that's
|
||||||
// been loaded has an existing checkPackageHandle.
|
// been loaded has an existing checkPackageHandle.
|
||||||
for _, m := range m {
|
if err := v.snapshot.checkWorkspacePackages(ctx, m); err != nil {
|
||||||
_, err := v.snapshot.checkPackageHandle(ctx, m.id, source.ParseFull)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.AddView(debugView{v})
|
debug.AddView(debugView{v})
|
||||||
return v, loadErr
|
return v, loadErr
|
||||||
}
|
}
|
||||||
|
63
internal/lsp/cache/snapshot.go
vendored
63
internal/lsp/cache/snapshot.go
vendored
@ -39,6 +39,10 @@ type snapshot struct {
|
|||||||
|
|
||||||
// actions maps an actionkey to its actionHandle.
|
// actions maps an actionkey to its actionHandle.
|
||||||
actions map[actionKey]*actionHandle
|
actions map[actionKey]*actionHandle
|
||||||
|
|
||||||
|
// workspacePackages contains the workspace's packages, which are loaded
|
||||||
|
// when the view is created.
|
||||||
|
workspacePackages map[packageID]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type packageKey struct {
|
type packageKey struct {
|
||||||
@ -99,16 +103,50 @@ func (s *snapshot) getPackages(uri source.FileURI, m source.ParseMode) (cphs []s
|
|||||||
return cphs
|
return cphs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkWorkspacePackages checks the initial set of packages loaded when
|
||||||
|
// the view is created. This is needed because
|
||||||
|
// (*snapshot).CheckPackageHandle makes the assumption that every package that's
|
||||||
|
// been loaded has an existing checkPackageHandle.
|
||||||
|
func (s *snapshot) checkWorkspacePackages(ctx context.Context, m []*metadata) error {
|
||||||
|
for _, m := range m {
|
||||||
|
_, err := s.checkPackageHandle(ctx, m.id, source.ParseFull)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.workspacePackages[m.id] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *snapshot) KnownPackages(ctx context.Context) []source.Package {
|
func (s *snapshot) KnownPackages(ctx context.Context) []source.Package {
|
||||||
// TODO(matloob): This function exists because KnownImportPaths can't
|
// TODO(matloob): This function exists because KnownImportPaths can't
|
||||||
// determine the import paths of all packages. Remove this function
|
// determine the import paths of all packages. Remove this function
|
||||||
// if KnownImportPaths gains that ability. That could happen if
|
// if KnownImportPaths gains that ability. That could happen if
|
||||||
// go list or go packages provide that information.
|
// go list or go packages provide that information.
|
||||||
|
pkgIDs := make(map[packageID]bool)
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
for _, m := range s.metadata {
|
||||||
|
pkgIDs[m.id] = true
|
||||||
|
}
|
||||||
|
// Add in all the workspacePackages in case the've been invalidated
|
||||||
|
// in the metadata since their initial load.
|
||||||
|
for id := range s.workspacePackages {
|
||||||
|
pkgIDs[id] = true
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
var results []source.Package
|
var results []source.Package
|
||||||
for _, cph := range s.packages {
|
for pkgID := range pkgIDs {
|
||||||
|
mode := source.ParseExported
|
||||||
|
if s.workspacePackages[pkgID] {
|
||||||
|
// Any package in our workspace should be loaded with ParseFull.
|
||||||
|
mode = source.ParseFull
|
||||||
|
}
|
||||||
|
cph, err := s.checkPackageHandle(ctx, pkgID, mode)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(ctx, fmt.Sprintf("cph.Check of %v", cph.m.pkgPath), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Check the package now if it's not checked yet.
|
// Check the package now if it's not checked yet.
|
||||||
// TODO(matloob): is this too slow?
|
// TODO(matloob): is this too slow?
|
||||||
pkg, err := cph.check(ctx)
|
pkg, err := cph.check(ctx)
|
||||||
@ -279,14 +317,15 @@ func (s *snapshot) clone(ctx context.Context, withoutURI *span.URI, withoutTypes
|
|||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
result := &snapshot{
|
result := &snapshot{
|
||||||
id: s.id + 1,
|
id: s.id + 1,
|
||||||
view: s.view,
|
view: s.view,
|
||||||
ids: make(map[span.URI][]packageID),
|
ids: make(map[span.URI][]packageID),
|
||||||
importedBy: make(map[packageID][]packageID),
|
importedBy: make(map[packageID][]packageID),
|
||||||
metadata: make(map[packageID]*metadata),
|
metadata: make(map[packageID]*metadata),
|
||||||
packages: make(map[packageKey]*checkPackageHandle),
|
packages: make(map[packageKey]*checkPackageHandle),
|
||||||
actions: make(map[actionKey]*actionHandle),
|
actions: make(map[actionKey]*actionHandle),
|
||||||
files: make(map[span.URI]source.FileHandle),
|
files: make(map[span.URI]source.FileHandle),
|
||||||
|
workspacePackages: make(map[packageID]bool),
|
||||||
}
|
}
|
||||||
// Copy all of the FileHandles except for the one that was invalidated.
|
// Copy all of the FileHandles except for the one that was invalidated.
|
||||||
for k, v := range s.files {
|
for k, v := range s.files {
|
||||||
@ -344,6 +383,10 @@ func (s *snapshot) clone(ctx context.Context, withoutURI *span.URI, withoutTypes
|
|||||||
}
|
}
|
||||||
result.metadata[k] = v
|
result.metadata[k] = v
|
||||||
}
|
}
|
||||||
|
// Copy the set of initally loaded packages
|
||||||
|
for k, v := range s.workspacePackages {
|
||||||
|
result.workspacePackages[k] = v
|
||||||
|
}
|
||||||
// Don't bother copying the importedBy graph,
|
// Don't bother copying the importedBy graph,
|
||||||
// as it changes each time we update metadata.
|
// as it changes each time we update metadata.
|
||||||
return result
|
return result
|
||||||
|
@ -8,10 +8,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/tool"
|
"golang.org/x/tools/internal/tool"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// references implements the references verb for gopls
|
// references implements the references verb for gopls
|
||||||
|
@ -6,17 +6,24 @@ package cmdtest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/cmd"
|
"golang.org/x/tools/internal/lsp/cmd"
|
||||||
"golang.org/x/tools/internal/tool"
|
"golang.org/x/tools/internal/tool"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *runner) References(t *testing.T, spn span.Span, itemList []span.Span) {
|
func (r *runner) References(t *testing.T, spn span.Span, itemList []span.Span) {
|
||||||
var expect string
|
var itemStrings []string
|
||||||
for _, i := range itemList {
|
for _, i := range itemList {
|
||||||
expect += fmt.Sprintln(i)
|
itemStrings = append(itemStrings, fmt.Sprint(i))
|
||||||
|
}
|
||||||
|
sort.Strings(itemStrings)
|
||||||
|
var expect string
|
||||||
|
for _, i := range itemStrings {
|
||||||
|
expect += i + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := spn.URI()
|
uri := spn.URI()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/types/objectpath"
|
||||||
"golang.org/x/tools/internal/telemetry/trace"
|
"golang.org/x/tools/internal/telemetry/trace"
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
@ -69,29 +70,47 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro
|
|||||||
mappedRange: rng,
|
mappedRange: rng,
|
||||||
}}, references...)
|
}}, references...)
|
||||||
}
|
}
|
||||||
for ident, obj := range info.Uses {
|
// TODO(matloob): This only needs to look into reverse-dependencies.
|
||||||
if obj == nil || !sameObj(obj, i.Declaration.obj) {
|
// Avoid checking types of other packages.
|
||||||
continue
|
for _, pkg := range i.Snapshot.KnownPackages(ctx) {
|
||||||
|
for ident, obj := range pkg.GetTypesInfo().Uses {
|
||||||
|
if obj == nil || !(sameObj(obj, i.Declaration.obj)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rng, err := posToMappedRange(ctx, i.Snapshot.View(), 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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
rng, err := posToMappedRange(ctx, i.Snapshot.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
|
return references, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sameObj returns true if obj is the same as declObj.
|
// sameObj returns true if obj is the same as declObj.
|
||||||
// Objects are the same if they have the some Pos and Name.
|
// Objects are the same if either they have they have objectpaths
|
||||||
|
// and their objectpath and package are the same; or if they don't
|
||||||
|
// have object paths and they have the same Pos and Name.
|
||||||
func sameObj(obj, declObj types.Object) bool {
|
func sameObj(obj, declObj types.Object) bool {
|
||||||
// TODO(suzmue): support the case where an identifier may have two different
|
// TODO(suzmue): support the case where an identifier may have two different
|
||||||
// declaration positions.
|
// declaration positions.
|
||||||
return obj.Pos() == declObj.Pos() && obj.Name() == declObj.Name()
|
if obj.Pkg() == nil || declObj.Pkg() == nil {
|
||||||
|
if obj.Pkg() != declObj.Pkg() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if obj.Pkg().Path() != declObj.Pkg().Path() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
objPath, operr := objectpath.For(obj)
|
||||||
|
declObjPath, doperr := objectpath.For(declObj)
|
||||||
|
if operr != nil || doperr != nil {
|
||||||
|
return obj.Pos() == declObj.Pos() && obj.Name() == declObj.Name()
|
||||||
|
}
|
||||||
|
return objPath == declObjPath
|
||||||
}
|
}
|
||||||
|
11
internal/lsp/testdata/references/other/other.go
vendored
Normal file
11
internal/lsp/testdata/references/other/other.go
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package other
|
||||||
|
|
||||||
|
import (
|
||||||
|
references "golang.org/x/tools/internal/lsp/references"
|
||||||
|
)
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
references.Q = "hello" //@mark(assignExpQ, "Q")
|
||||||
|
bob := func(_ string) {}
|
||||||
|
bob(references.Q) //@mark(bobExpQ, "Q")
|
||||||
|
}
|
8
internal/lsp/testdata/references/refs.go
vendored
8
internal/lsp/testdata/references/refs.go
vendored
@ -1,17 +1,19 @@
|
|||||||
package refs
|
package refs
|
||||||
|
|
||||||
type i int //@mark(typeInt, "int"),refs("int", typeInt, argInt, returnInt)
|
type i int //@mark(typeI, "i"),refs("i", typeI, argI, returnI)
|
||||||
|
|
||||||
func _(_ int) []bool { //@mark(argInt, "int")
|
func _(_ i) []bool { //@mark(argI, "i")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func _(_ string) int { //@mark(returnInt, "int")
|
func _(_ []byte) i { //@mark(returnI, "i")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var q string //@mark(declQ, "q"),refs("q", declQ, assignQ, bobQ)
|
var q string //@mark(declQ, "q"),refs("q", declQ, assignQ, bobQ)
|
||||||
|
|
||||||
|
var Q string //@mark(declExpQ, "Q"), refs("Q", declExpQ, assignExpQ, bobExpQ)
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
q = "hello" //@mark(assignQ, "q")
|
q = "hello" //@mark(assignQ, "q")
|
||||||
bob := func(_ string) {}
|
bob := func(_ string) {}
|
||||||
|
2
internal/lsp/testdata/summary.txt.golden
vendored
2
internal/lsp/testdata/summary.txt.golden
vendored
@ -14,7 +14,7 @@ SuggestedFixCount = 1
|
|||||||
DefinitionsCount = 38
|
DefinitionsCount = 38
|
||||||
TypeDefinitionsCount = 2
|
TypeDefinitionsCount = 2
|
||||||
HighlightsCount = 2
|
HighlightsCount = 2
|
||||||
ReferencesCount = 6
|
ReferencesCount = 7
|
||||||
RenamesCount = 20
|
RenamesCount = 20
|
||||||
PrepareRenamesCount = 8
|
PrepareRenamesCount = 8
|
||||||
SymbolsCount = 1
|
SymbolsCount = 1
|
||||||
|
Loading…
Reference in New Issue
Block a user