blob: 9fe59e4aec9a57306d6a3d37308798d7c6c58cb4 [file] [log] [blame]
// 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"
"go/types"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/telemetry/log"
errors "golang.org/x/xerrors"
)
func (i *IdentifierInfo) Implementation(ctx context.Context) ([]protocol.Location, error) {
ctx = telemetry.Package.With(ctx, i.pkg.ID())
impls, err := i.implementations(ctx)
if err != nil {
return nil, err
}
var locations []protocol.Location
for _, impl := range impls {
if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
continue
}
file, _, err := i.Snapshot.View().FindPosInPackage(impl.pkg, impl.obj.Pos())
if err != nil {
log.Error(ctx, "Error getting file for object", err)
continue
}
ident, err := findIdentifier(i.Snapshot, impl.pkg, file, impl.obj.Pos())
if err != nil {
log.Error(ctx, "Error getting ident for object", err)
continue
}
decRange, err := ident.Declaration.Range()
if err != nil {
log.Error(ctx, "Error getting range for object", err)
continue
}
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
})
}
return locations, nil
}
var ErrNotAnInterface = errors.New("not an interface or interface method")
func (i *IdentifierInfo) implementations(ctx context.Context) ([]implementation, error) {
var (
T *types.Interface
method *types.Func
)
switch obj := i.Declaration.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)
)
for _, pkg := range i.Snapshot.KnownPackages(ctx) {
pkgs[pkg.GetTypes()] = pkg
info := pkg.GetTypesInfo()
for _, obj := range info.Defs {
// We ignore aliases 'type M = N' to avoid duplicate reporting
// of the Named type N.
if obj, ok := obj.(*types.TypeName); ok && !obj.IsAlias() {
// We skip interface types since we only want concrete
// implementations.
if named, ok := obj.Type().(*types.Named); ok && !isInterface(named) {
allNamed = append(allNamed, named)
}
}
}
}
var (
impls []implementation
seen = make(map[types.Object]bool)
)
// 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()
}
if obj == method || seen[obj] {
continue
}
seen[obj] = 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
}