2019-10-28 13:16:55 -06:00
|
|
|
// 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"
|
2019-12-17 11:22:35 -07:00
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
2019-10-28 13:16:55 -06:00
|
|
|
"go/types"
|
|
|
|
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-12-17 11:22:35 -07:00
|
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
2019-12-07 23:07:30 -07:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-10-28 13:16:55 -06:00
|
|
|
)
|
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]protocol.Location, error) {
|
|
|
|
ctx, done := trace.StartSpan(ctx, "source.Implementation")
|
|
|
|
defer done()
|
2019-11-26 13:03:20 -07:00
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
impls, err := implementations(ctx, s, f, pp)
|
2019-10-28 13:16:55 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-05 15:03:09 -07:00
|
|
|
var locations []protocol.Location
|
2019-12-05 22:34:17 -07:00
|
|
|
for _, impl := range impls {
|
|
|
|
if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
|
2019-11-11 14:51:47 -07:00
|
|
|
continue
|
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
rng, err := objToMappedRange(s.View(), impl.pkg, impl.obj)
|
2019-11-05 15:03:09 -07:00
|
|
|
if err != nil {
|
2020-01-07 19:37:41 -07:00
|
|
|
return nil, err
|
2019-11-11 14:51:47 -07:00
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
pr, err := rng.Range()
|
2019-11-05 15:03:09 -07:00
|
|
|
if err != nil {
|
2020-01-07 19:37:41 -07:00
|
|
|
return nil, err
|
2019-11-05 15:03:09 -07:00
|
|
|
}
|
|
|
|
locations = append(locations, protocol.Location{
|
2020-02-12 14:36:46 -07:00
|
|
|
URI: protocol.URIFromSpanURI(rng.URI()),
|
2019-12-17 11:22:35 -07:00
|
|
|
Range: pr,
|
2019-11-05 15:03:09 -07:00
|
|
|
})
|
|
|
|
}
|
2019-10-28 13:16:55 -06:00
|
|
|
return locations, nil
|
|
|
|
}
|
2019-11-25 12:40:42 -07:00
|
|
|
|
2019-12-05 22:34:17 -07:00
|
|
|
var ErrNotAnInterface = errors.New("not an interface or interface method")
|
2019-12-07 23:07:30 -07:00
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
|
2019-12-05 22:34:17 -07:00
|
|
|
var (
|
2019-12-17 22:06:31 -07:00
|
|
|
impls []qualifiedObject
|
2019-12-17 11:22:35 -07:00
|
|
|
seen = make(map[token.Position]bool)
|
|
|
|
fset = s.View().Session().Cache().FileSet()
|
2019-12-05 22:34:17 -07:00
|
|
|
)
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
|
2019-12-17 11:22:35 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-12-05 22:34:17 -07:00
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
for _, qo := range qos {
|
2019-12-17 11:22:35 -07:00
|
|
|
var (
|
|
|
|
T *types.Interface
|
|
|
|
method *types.Func
|
|
|
|
)
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
switch obj := qo.obj.(type) {
|
2019-12-17 11:22:35 -07:00
|
|
|
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)
|
|
|
|
}
|
2019-12-05 22:34:17 -07:00
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
if T == nil {
|
|
|
|
return nil, ErrNotAnInterface
|
|
|
|
}
|
2019-10-28 13:16:55 -06:00
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
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)
|
|
|
|
)
|
2019-12-19 12:31:39 -07:00
|
|
|
knownPkgs, err := s.KnownPackages(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, ph := range knownPkgs {
|
2019-12-27 14:44:33 -07:00
|
|
|
pkg, err := ph.Check(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
pkgs[pkg.GetTypes()] = pkg
|
|
|
|
info := pkg.GetTypesInfo()
|
|
|
|
for _, obj := range info.Defs {
|
2019-12-30 14:35:40 -07:00
|
|
|
obj, ok := obj.(*types.TypeName)
|
2019-12-17 11:22:35 -07:00
|
|
|
// We ignore aliases 'type M = N' to avoid duplicate reporting
|
|
|
|
// of the Named type N.
|
2019-12-30 14:35:40 -07:00
|
|
|
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
|
2019-11-11 14:51:47 -07:00
|
|
|
}
|
2019-12-30 14:35:40 -07:00
|
|
|
allNamed = append(allNamed, named)
|
2019-10-28 13:16:55 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
// Find all the named types that implement our interface.
|
|
|
|
for _, U := range allNamed {
|
|
|
|
var concrete types.Type = U
|
2019-12-05 22:34:17 -07:00
|
|
|
if !types.AssignableTo(concrete, T) {
|
2019-12-17 11:22:35 -07:00
|
|
|
// We also accept T if *T implements our interface.
|
|
|
|
concrete = types.NewPointer(concrete)
|
|
|
|
if !types.AssignableTo(concrete, T) {
|
|
|
|
continue
|
|
|
|
}
|
2019-10-28 13:16:55 -06:00
|
|
|
}
|
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
var obj types.Object = U.Obj()
|
|
|
|
if method != nil {
|
|
|
|
obj = types.NewMethodSet(concrete).Lookup(method.Pkg(), method.Name()).Obj()
|
|
|
|
}
|
2019-12-05 22:34:17 -07:00
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
pos := fset.Position(obj.Pos())
|
|
|
|
if obj == method || seen[pos] {
|
|
|
|
continue
|
|
|
|
}
|
2019-12-05 22:34:17 -07:00
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
seen[pos] = true
|
2019-12-05 22:34:17 -07:00
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
impls = append(impls, qualifiedObject{
|
2019-12-17 11:22:35 -07:00
|
|
|
obj: obj,
|
|
|
|
pkg: pkgs[obj.Pkg()],
|
|
|
|
})
|
|
|
|
}
|
2019-11-05 15:03:09 -07:00
|
|
|
}
|
2019-12-05 22:34:17 -07:00
|
|
|
|
|
|
|
return impls, nil
|
2019-10-28 13:16:55 -06:00
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
type qualifiedObject struct {
|
2019-12-05 22:34:17 -07:00
|
|
|
obj types.Object
|
|
|
|
|
|
|
|
// pkg is the Package that contains obj's definition.
|
|
|
|
pkg Package
|
2019-12-17 22:06:31 -07:00
|
|
|
|
|
|
|
// node is the *ast.Ident or *ast.ImportSpec we followed to find obj, if any.
|
|
|
|
node ast.Node
|
|
|
|
|
|
|
|
// sourcePkg is the Package that contains node, if any.
|
|
|
|
sourcePkg Package
|
2019-10-28 13:16:55 -06:00
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
// qualifiedObjsAtProtocolPos returns info for all the type.Objects
|
|
|
|
// referenced at the given position. An object will be returned for
|
|
|
|
// every package that the file belongs to.
|
|
|
|
func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
|
2019-12-17 11:22:35 -07:00
|
|
|
phs, err := s.PackageHandles(ctx, f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
var qualifiedObjs []qualifiedObject
|
2019-12-17 11:22:35 -07:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
path := pathEnclosingObjNode(astFile, pos)
|
|
|
|
if path == nil {
|
2019-12-17 11:22:35 -07:00
|
|
|
return nil, ErrNoIdentFound
|
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
var objs []types.Object
|
|
|
|
switch leaf := path[0].(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
// If leaf represents an implicit type switch object or the type
|
|
|
|
// switch "assign" variable, expand to all of the type switch's
|
|
|
|
// implicit objects.
|
|
|
|
if implicits := typeSwitchImplicits(pkg, path); len(implicits) > 0 {
|
|
|
|
objs = append(objs, implicits...)
|
|
|
|
} else {
|
|
|
|
obj := pkg.GetTypesInfo().ObjectOf(leaf)
|
|
|
|
if obj == nil {
|
|
|
|
return nil, fmt.Errorf("no object for %q", leaf.Name)
|
|
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
|
|
}
|
|
|
|
case *ast.ImportSpec:
|
|
|
|
// Look up the implicit *types.PkgName.
|
|
|
|
obj := pkg.GetTypesInfo().Implicits[leaf]
|
|
|
|
if obj == nil {
|
|
|
|
return nil, fmt.Errorf("no object for import %q", importPath(leaf))
|
|
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
pkgs := make(map[*types.Package]Package)
|
|
|
|
pkgs[pkg.GetTypes()] = pkg
|
|
|
|
for _, imp := range pkg.Imports() {
|
|
|
|
pkgs[imp.GetTypes()] = imp
|
2019-12-17 11:22:35 -07:00
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
for _, obj := range objs {
|
|
|
|
qualifiedObjs = append(qualifiedObjs, qualifiedObject{
|
|
|
|
obj: obj,
|
|
|
|
pkg: pkgs[obj.Pkg()],
|
|
|
|
sourcePkg: pkg,
|
|
|
|
node: path[0],
|
|
|
|
})
|
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
return qualifiedObjs, nil
|
2019-12-17 11:22:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:10:59 -07:00
|
|
|
file, _, m, _, err := pgh.Cached()
|
2019-12-17 11:22:35 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
// pathEnclosingObjNode returns the AST path to the object-defining
|
|
|
|
// node associated with pos. "Object-defining" means either an
|
|
|
|
// *ast.Ident mapped directly to a types.Object or an ast.Node mapped
|
|
|
|
// implicitly to a types.Object.
|
|
|
|
func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
|
2019-12-17 11:22:35 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
path = append(path, n)
|
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
switch n := n.(type) {
|
|
|
|
case *ast.Ident:
|
2019-12-17 22:06:31 -07:00
|
|
|
// Include the position directly after identifier. This handles
|
|
|
|
// the common case where the cursor is right after the
|
|
|
|
// identifier the user is currently typing. Previously we
|
|
|
|
// handled this by calling astutil.PathEnclosingInterval twice,
|
|
|
|
// once for "pos" and once for "pos-1".
|
2019-12-17 11:22:35 -07:00
|
|
|
found = n.Pos() <= pos && pos <= n.End()
|
2019-12-17 22:06:31 -07:00
|
|
|
case *ast.ImportSpec:
|
|
|
|
if n.Path.Pos() <= pos && pos < n.Path.End() {
|
|
|
|
found = true
|
|
|
|
// If import spec has a name, add name to path even though
|
|
|
|
// position isn't in the name.
|
|
|
|
if n.Name != nil {
|
|
|
|
path = append(path, n.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ast.StarExpr:
|
|
|
|
// Follow star expressions to the inner identifer.
|
|
|
|
if pos == n.Star {
|
|
|
|
pos = n.X.Pos()
|
|
|
|
}
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
// If pos is on the ".", move it into the selector.
|
|
|
|
if pos == n.X.End() {
|
|
|
|
pos = n.Sel.Pos()
|
|
|
|
}
|
2019-12-17 11:22:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return !found
|
|
|
|
})
|
|
|
|
|
2019-12-17 22:06:31 -07:00
|
|
|
if len(path) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse path so leaf is first element.
|
|
|
|
for i := 0; i < len(path)/2; i++ {
|
|
|
|
path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i]
|
|
|
|
}
|
|
|
|
|
2019-12-17 11:22:35 -07:00
|
|
|
return path
|
|
|
|
}
|