mirror of
https://github.com/golang/go
synced 2024-11-18 21:44:45 -07:00
7201abb308
The initial workspace load was happening when a view was created, in serial. It should really just be kicked off in a separate goroutine once we create a new view. Implementing this change required some other significant changes, particularly the additional work being done by the WorkspacePackageIDs method. Some other changes had to be made while debugging. In particular, the modification to the circular dependencies test was a consequence of golang/go#36265. Change-Id: I97586c9574f6c4106172d7983e4c6fad412e6aa1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/212102 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
263 lines
5.7 KiB
Go
263 lines
5.7 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]protocol.Location, error) {
|
|
ctx, done := trace.StartSpan(ctx, "source.Implementation")
|
|
defer done()
|
|
|
|
impls, err := implementations(ctx, s, f, pp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var locations []protocol.Location
|
|
for _, impl := range impls {
|
|
if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
|
|
continue
|
|
}
|
|
|
|
rng, err := objToMappedRange(s.View(), impl.pkg, impl.obj)
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting range for object", err)
|
|
continue
|
|
}
|
|
|
|
pr, err := rng.Range()
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting protocol range for object", err)
|
|
continue
|
|
}
|
|
|
|
locations = append(locations, protocol.Location{
|
|
URI: protocol.NewURI(rng.URI()),
|
|
Range: pr,
|
|
})
|
|
}
|
|
return locations, nil
|
|
}
|
|
|
|
var ErrNotAnInterface = errors.New("not an interface or interface method")
|
|
|
|
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]implementation, error) {
|
|
var (
|
|
impls []implementation
|
|
seen = make(map[token.Position]bool)
|
|
fset = s.View().Session().Cache().FileSet()
|
|
)
|
|
|
|
objs, err := objectsAtProtocolPos(ctx, s, f, pp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, obj := range objs {
|
|
var (
|
|
T *types.Interface
|
|
method *types.Func
|
|
)
|
|
|
|
switch obj := obj.(type) {
|
|
case *types.Func:
|
|
method = obj
|
|
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
|
T, _ = recv.Type().Underlying().(*types.Interface)
|
|
}
|
|
case *types.TypeName:
|
|
T, _ = obj.Type().Underlying().(*types.Interface)
|
|
}
|
|
|
|
if T == nil {
|
|
return nil, ErrNotAnInterface
|
|
}
|
|
|
|
if T.NumMethods() == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Find all named types, even local types (which can have methods
|
|
// due to promotion).
|
|
var (
|
|
allNamed []*types.Named
|
|
pkgs = make(map[*types.Package]Package)
|
|
)
|
|
knownPkgs, err := s.KnownPackages(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ph := range knownPkgs {
|
|
pkg, err := ph.Check(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkgs[pkg.GetTypes()] = pkg
|
|
info := pkg.GetTypesInfo()
|
|
for _, obj := range info.Defs {
|
|
obj, ok := obj.(*types.TypeName)
|
|
// We ignore aliases 'type M = N' to avoid duplicate reporting
|
|
// of the Named type N.
|
|
if !ok || obj.IsAlias() {
|
|
continue
|
|
}
|
|
named, ok := obj.Type().(*types.Named)
|
|
// We skip interface types since we only want concrete
|
|
// implementations.
|
|
if !ok || isInterface(named) {
|
|
continue
|
|
}
|
|
allNamed = append(allNamed, named)
|
|
}
|
|
}
|
|
|
|
// Find all the named types that implement our interface.
|
|
for _, U := range allNamed {
|
|
var concrete types.Type = U
|
|
if !types.AssignableTo(concrete, T) {
|
|
// We also accept T if *T implements our interface.
|
|
concrete = types.NewPointer(concrete)
|
|
if !types.AssignableTo(concrete, T) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var obj types.Object = U.Obj()
|
|
if method != nil {
|
|
obj = types.NewMethodSet(concrete).Lookup(method.Pkg(), method.Name()).Obj()
|
|
}
|
|
|
|
pos := fset.Position(obj.Pos())
|
|
if obj == method || seen[pos] {
|
|
continue
|
|
}
|
|
|
|
seen[pos] = true
|
|
|
|
impls = append(impls, implementation{
|
|
obj: obj,
|
|
pkg: pkgs[obj.Pkg()],
|
|
})
|
|
}
|
|
}
|
|
|
|
return impls, nil
|
|
}
|
|
|
|
type implementation struct {
|
|
// obj is the implementation, either a *types.TypeName or *types.Func.
|
|
obj types.Object
|
|
|
|
// pkg is the Package that contains obj's definition.
|
|
pkg Package
|
|
}
|
|
|
|
// objectsAtProtocolPos returns all the type.Objects referenced at the given position.
|
|
// An object will be returned for every package that the file belongs to.
|
|
func objectsAtProtocolPos(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]types.Object, error) {
|
|
phs, err := s.PackageHandles(ctx, f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var objs []types.Object
|
|
|
|
// Check all the packages that the file belongs to.
|
|
for _, ph := range phs {
|
|
pkg, err := ph.Check(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
astFile, pos, err := getASTFile(pkg, f, pp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
path := pathEnclosingIdent(astFile, pos)
|
|
if len(path) == 0 {
|
|
return nil, ErrNoIdentFound
|
|
}
|
|
|
|
ident := path[len(path)-1].(*ast.Ident)
|
|
|
|
obj := pkg.GetTypesInfo().ObjectOf(ident)
|
|
if obj == nil {
|
|
return nil, fmt.Errorf("no object for %q", ident.Name)
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
}
|
|
|
|
return objs, nil
|
|
}
|
|
|
|
func getASTFile(pkg Package, f FileHandle, pos protocol.Position) (*ast.File, token.Pos, error) {
|
|
pgh, err := pkg.File(f.Identity().URI)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
file, m, _, err := pgh.Cached()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
spn, err := m.PointSpan(pos)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
rng, err := spn.Range(m.Converter)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return file, rng.Start, nil
|
|
}
|
|
|
|
// pathEnclosingIdent returns the ast path to the node that contains pos.
|
|
// It is similar to astutil.PathEnclosingInterval, but simpler, and it
|
|
// matches *ast.Ident nodes if pos is equal to node.End().
|
|
func pathEnclosingIdent(f *ast.File, pos token.Pos) []ast.Node {
|
|
var (
|
|
path []ast.Node
|
|
found bool
|
|
)
|
|
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
if found {
|
|
return false
|
|
}
|
|
|
|
if n == nil {
|
|
path = path[:len(path)-1]
|
|
return false
|
|
}
|
|
|
|
switch n := n.(type) {
|
|
case *ast.Ident:
|
|
found = n.Pos() <= pos && pos <= n.End()
|
|
}
|
|
|
|
path = append(path, n)
|
|
|
|
return !found
|
|
})
|
|
|
|
return path
|
|
}
|