1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:54:43 -07:00

internal/lsp/source: support inverse "implementations"

Now "implementations" supports finding interfaces implemented by the
specified concrete type. This is the inverse of what "implementations"
normally does. There isn't currently a better place in LSP to put this
functionality. The reverse lookup isn't an important feature for most
languages because types often must explicitly declare what interfaces
they implement.

An argument can be made that this functionality fits better into find-
references, but it is still a stretch. Plus, that would require
find-references to search all packages instead of just transitive
dependents.

Updates golang/go#35550.

Change-Id: I72dc78ef52b5bf501da2f39692e99cd304b5406e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/219678
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Muir Manders 2020-02-15 19:25:45 -08:00 committed by Rebecca Stambler
parent 49e4010bbf
commit ae0473a2ca
4 changed files with 88 additions and 55 deletions

View File

@ -46,8 +46,10 @@ func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.P
return locations, nil
}
var ErrNotAnInterface = errors.New("not an interface or interface method")
var ErrNotAType = errors.New("not a type name or method")
// implementations returns the concrete implementations of the specified
// interface, or the interfaces implemented by the specified concrete type.
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
var (
impls []qualifiedObject
@ -62,25 +64,25 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
for _, qo := range qos {
var (
T *types.Interface
method *types.Func
queryType types.Type
queryMethod *types.Func
)
switch obj := qo.obj.(type) {
case *types.Func:
method = obj
queryMethod = obj
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
T, _ = recv.Type().Underlying().(*types.Interface)
queryType = ensurePointer(recv.Type())
}
case *types.TypeName:
T, _ = obj.Type().Underlying().(*types.Interface)
queryType = ensurePointer(obj.Type())
}
if T == nil {
return nil, ErrNotAnInterface
if queryType == nil {
return nil, ErrNotAType
}
if T.NumMethods() == 0 {
if types.NewMethodSet(queryType).Len() == 0 {
return nil, nil
}
@ -108,42 +110,48 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
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
if named, ok := obj.Type().(*types.Named); ok {
allNamed = append(allNamed, named)
}
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) {
// Find all the named types that match our query.
for _, named := range allNamed {
var (
candObj types.Object = named.Obj()
candType = ensurePointer(named)
)
if !concreteImplementsIntf(candType, queryType) {
continue
}
ms := types.NewMethodSet(candType)
if ms.Len() == 0 {
// Skip empty interfaces.
continue
}
// If client queried a method, look up corresponding candType method.
if queryMethod != nil {
sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
if sel == nil {
continue
}
candObj = sel.Obj()
}
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] {
pos := fset.Position(candObj.Pos())
if candObj == queryMethod || seen[pos] {
continue
}
seen[pos] = true
impls = append(impls, qualifiedObject{
obj: obj,
pkg: pkgs[obj.Pkg()],
obj: candObj,
pkg: pkgs[candObj.Pkg()],
})
}
}
@ -151,6 +159,35 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
return impls, nil
}
// concreteImplementsIntf returns true if a is an interface type implemented by
// concrete type b, or vice versa.
func concreteImplementsIntf(a, b types.Type) bool {
aIsIntf, bIsIntf := isInterface(a), isInterface(b)
// Make sure exactly one is an interface type.
if aIsIntf == bIsIntf {
return false
}
// Rearrange if needed so "a" is the concrete type.
if aIsIntf {
a, b = b, a
}
return types.AssignableTo(a, b)
}
// ensurePointer wraps T in a *types.Pointer if T is a named, non-interface
// type. This is useful to make sure you consider a named type's full method
// set.
func ensurePointer(T types.Type) types.Type {
if _, ok := T.(*types.Named); ok && !isInterface(T) {
return types.NewPointer(T)
}
return T
}
type qualifiedObject struct {
obj types.Object

View File

@ -2,32 +2,30 @@ package implementation
import "golang.org/x/tools/internal/lsp/implementation/other"
type ImpP struct{} //@ImpP
type ImpP struct{} //@ImpP,implementations("ImpP", Laugher, OtherLaugher)
func (*ImpP) Laugh() { //@mark(LaughP, "Laugh")
func (*ImpP) Laugh() { //@mark(LaughP, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
}
type ImpS struct{} //@ImpS
type ImpS struct{} //@ImpS,implementations("ImpS", Laugher, OtherLaugher)
func (ImpS) Laugh() { //@mark(LaughS, "Laugh")
func (ImpS) Laugh() { //@mark(LaughS, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
}
type ImpI interface {
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
type Laugher interface { //@Laugher,implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
Laugh() //@Laugh,implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
}
type Laugher interface { //@implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
}
type Foo struct {
type Foo struct { //@implementations("Foo", Joker)
other.Foo
}
type U interface {
U() //@implementations("U", ImpU)
type Joker interface { //@Joker
Joke() //@Joke,implementations("Joke", ImpJoker)
}
type cryer int
type cryer int //@implementations("cryer", Cryer)
func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry")
func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry)
type Empty interface{} //@implementations("Empty")

View File

@ -10,20 +10,18 @@ type ImpS struct{} //@mark(OtherImpS, "ImpS")
func (ImpS) Laugh() { //@mark(OtherLaughS, "Laugh")
}
type ImpI interface {
Laugh()
type ImpI interface { //@mark(OtherLaugher, "ImpI")
Laugh() //@mark(OtherLaugh, "Laugh")
}
type Foo struct {
type Foo struct { //@implementations("Foo", Joker)
}
func (Foo) U() { //@mark(ImpU, "U")
func (Foo) Joke() { //@mark(ImpJoker, "Joke"),implementations("Joke", Joke)
}
type CryType int
const Sob CryType = 1
type Cryer interface {
Cry(CryType) //@implementations("Cry", CryImpl)
type Cryer interface { //@Cryer
Cry(CryType) //@Cry,implementations("Cry", CryImpl)
}

View File

@ -24,5 +24,5 @@ FuzzyWorkspaceSymbolsCount = 3
CaseSensitiveWorkspaceSymbolsCount = 2
SignaturesCount = 23
LinksCount = 8
ImplementationsCount = 5
ImplementationsCount = 14