1
0
mirror of https://github.com/golang/go synced 2024-11-18 19:44:46 -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 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) { func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
var ( var (
impls []qualifiedObject impls []qualifiedObject
@ -62,25 +64,25 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
for _, qo := range qos { for _, qo := range qos {
var ( var (
T *types.Interface queryType types.Type
method *types.Func queryMethod *types.Func
) )
switch obj := qo.obj.(type) { switch obj := qo.obj.(type) {
case *types.Func: case *types.Func:
method = obj queryMethod = obj
if recv := obj.Type().(*types.Signature).Recv(); recv != nil { if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
T, _ = recv.Type().Underlying().(*types.Interface) queryType = ensurePointer(recv.Type())
} }
case *types.TypeName: case *types.TypeName:
T, _ = obj.Type().Underlying().(*types.Interface) queryType = ensurePointer(obj.Type())
} }
if T == nil { if queryType == nil {
return nil, ErrNotAnInterface return nil, ErrNotAType
} }
if T.NumMethods() == 0 { if types.NewMethodSet(queryType).Len() == 0 {
return nil, nil return nil, nil
} }
@ -108,42 +110,48 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
if !ok || obj.IsAlias() { if !ok || obj.IsAlias() {
continue continue
} }
named, ok := obj.Type().(*types.Named) if named, ok := obj.Type().(*types.Named); ok {
// We skip interface types since we only want concrete allNamed = append(allNamed, named)
// implementations.
if !ok || isInterface(named) {
continue
} }
allNamed = append(allNamed, named)
} }
} }
// Find all the named types that implement our interface. // Find all the named types that match our query.
for _, U := range allNamed { for _, named := range allNamed {
var concrete types.Type = U var (
if !types.AssignableTo(concrete, T) { candObj types.Object = named.Obj()
// We also accept T if *T implements our interface. candType = ensurePointer(named)
concrete = types.NewPointer(concrete) )
if !types.AssignableTo(concrete, T) {
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 continue
} }
candObj = sel.Obj()
} }
var obj types.Object = U.Obj() pos := fset.Position(candObj.Pos())
if method != nil { if candObj == queryMethod || seen[pos] {
obj = types.NewMethodSet(concrete).Lookup(method.Pkg(), method.Name()).Obj()
}
pos := fset.Position(obj.Pos())
if obj == method || seen[pos] {
continue continue
} }
seen[pos] = true seen[pos] = true
impls = append(impls, qualifiedObject{ impls = append(impls, qualifiedObject{
obj: obj, obj: candObj,
pkg: pkgs[obj.Pkg()], pkg: pkgs[candObj.Pkg()],
}) })
} }
} }
@ -151,6 +159,35 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
return impls, nil 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 { type qualifiedObject struct {
obj types.Object obj types.Object

View File

@ -2,32 +2,30 @@ package implementation
import "golang.org/x/tools/internal/lsp/implementation/other" 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 { type Laugher interface { //@Laugher,implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS) Laugh() //@Laugh,implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
} }
type Laugher interface { //@implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS) type Foo struct { //@implementations("Foo", Joker)
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
}
type Foo struct {
other.Foo other.Foo
} }
type U interface { type Joker interface { //@Joker
U() //@implementations("U", ImpU) 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") func (ImpS) Laugh() { //@mark(OtherLaughS, "Laugh")
} }
type ImpI interface { type ImpI interface { //@mark(OtherLaugher, "ImpI")
Laugh() 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 type CryType int
const Sob CryType = 1 type Cryer interface { //@Cryer
Cry(CryType) //@Cry,implementations("Cry", CryImpl)
type Cryer interface {
Cry(CryType) //@implementations("Cry", CryImpl)
} }

View File

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