Skip to content

Commit ae0473a

Browse files
muirdmstamblerre
authored andcommitted
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 49e4010 commit ae0473a

File tree

4 files changed

+88
-55
lines changed

4 files changed

+88
-55
lines changed

internal/lsp/source/implementation.go

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.P
4646
return locations, nil
4747
}
4848

49-
var ErrNotAnInterface = errors.New("not an interface or interface method")
49+
var ErrNotAType = errors.New("not a type name or method")
5050

51+
// implementations returns the concrete implementations of the specified
52+
// interface, or the interfaces implemented by the specified concrete type.
5153
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
5254
var (
5355
impls []qualifiedObject
@@ -62,25 +64,25 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
6264

6365
for _, qo := range qos {
6466
var (
65-
T *types.Interface
66-
method *types.Func
67+
queryType types.Type
68+
queryMethod *types.Func
6769
)
6870

6971
switch obj := qo.obj.(type) {
7072
case *types.Func:
71-
method = obj
73+
queryMethod = obj
7274
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
73-
T, _ = recv.Type().Underlying().(*types.Interface)
75+
queryType = ensurePointer(recv.Type())
7476
}
7577
case *types.TypeName:
76-
T, _ = obj.Type().Underlying().(*types.Interface)
78+
queryType = ensurePointer(obj.Type())
7779
}
7880

79-
if T == nil {
80-
return nil, ErrNotAnInterface
81+
if queryType == nil {
82+
return nil, ErrNotAType
8183
}
8284

83-
if T.NumMethods() == 0 {
85+
if types.NewMethodSet(queryType).Len() == 0 {
8486
return nil, nil
8587
}
8688

@@ -108,49 +110,84 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
108110
if !ok || obj.IsAlias() {
109111
continue
110112
}
111-
named, ok := obj.Type().(*types.Named)
112-
// We skip interface types since we only want concrete
113-
// implementations.
114-
if !ok || isInterface(named) {
115-
continue
113+
if named, ok := obj.Type().(*types.Named); ok {
114+
allNamed = append(allNamed, named)
116115
}
117-
allNamed = append(allNamed, named)
118116
}
119117
}
120118

121-
// Find all the named types that implement our interface.
122-
for _, U := range allNamed {
123-
var concrete types.Type = U
124-
if !types.AssignableTo(concrete, T) {
125-
// We also accept T if *T implements our interface.
126-
concrete = types.NewPointer(concrete)
127-
if !types.AssignableTo(concrete, T) {
128-
continue
129-
}
119+
// Find all the named types that match our query.
120+
for _, named := range allNamed {
121+
var (
122+
candObj types.Object = named.Obj()
123+
candType = ensurePointer(named)
124+
)
125+
126+
if !concreteImplementsIntf(candType, queryType) {
127+
continue
130128
}
131129

132-
var obj types.Object = U.Obj()
133-
if method != nil {
134-
obj = types.NewMethodSet(concrete).Lookup(method.Pkg(), method.Name()).Obj()
130+
ms := types.NewMethodSet(candType)
131+
if ms.Len() == 0 {
132+
// Skip empty interfaces.
133+
continue
135134
}
136135

137-
pos := fset.Position(obj.Pos())
138-
if obj == method || seen[pos] {
136+
// If client queried a method, look up corresponding candType method.
137+
if queryMethod != nil {
138+
sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
139+
if sel == nil {
140+
continue
141+
}
142+
candObj = sel.Obj()
143+
}
144+
145+
pos := fset.Position(candObj.Pos())
146+
if candObj == queryMethod || seen[pos] {
139147
continue
140148
}
141149

142150
seen[pos] = true
143151

144152
impls = append(impls, qualifiedObject{
145-
obj: obj,
146-
pkg: pkgs[obj.Pkg()],
153+
obj: candObj,
154+
pkg: pkgs[candObj.Pkg()],
147155
})
148156
}
149157
}
150158

151159
return impls, nil
152160
}
153161

162+
// concreteImplementsIntf returns true if a is an interface type implemented by
163+
// concrete type b, or vice versa.
164+
func concreteImplementsIntf(a, b types.Type) bool {
165+
aIsIntf, bIsIntf := isInterface(a), isInterface(b)
166+
167+
// Make sure exactly one is an interface type.
168+
if aIsIntf == bIsIntf {
169+
return false
170+
}
171+
172+
// Rearrange if needed so "a" is the concrete type.
173+
if aIsIntf {
174+
a, b = b, a
175+
}
176+
177+
return types.AssignableTo(a, b)
178+
}
179+
180+
// ensurePointer wraps T in a *types.Pointer if T is a named, non-interface
181+
// type. This is useful to make sure you consider a named type's full method
182+
// set.
183+
func ensurePointer(T types.Type) types.Type {
184+
if _, ok := T.(*types.Named); ok && !isInterface(T) {
185+
return types.NewPointer(T)
186+
}
187+
188+
return T
189+
}
190+
154191
type qualifiedObject struct {
155192
obj types.Object
156193

internal/lsp/testdata/lsp/primarymod/implementation/implementation.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,30 @@ package implementation
22

33
import "golang.org/x/tools/internal/lsp/implementation/other"
44

5-
type ImpP struct{} //@ImpP
5+
type ImpP struct{} //@ImpP,implementations("ImpP", Laugher, OtherLaugher)
66

7-
func (*ImpP) Laugh() { //@mark(LaughP, "Laugh")
7+
func (*ImpP) Laugh() { //@mark(LaughP, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
88
}
99

10-
type ImpS struct{} //@ImpS
10+
type ImpS struct{} //@ImpS,implementations("ImpS", Laugher, OtherLaugher)
1111

12-
func (ImpS) Laugh() { //@mark(LaughS, "Laugh")
12+
func (ImpS) Laugh() { //@mark(LaughS, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
1313
}
1414

15-
type ImpI interface {
16-
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
15+
type Laugher interface { //@Laugher,implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
16+
Laugh() //@Laugh,implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
1717
}
1818

19-
type Laugher interface { //@implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
20-
Laugh() //@implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
21-
}
22-
23-
type Foo struct {
19+
type Foo struct { //@implementations("Foo", Joker)
2420
other.Foo
2521
}
2622

27-
type U interface {
28-
U() //@implementations("U", ImpU)
23+
type Joker interface { //@Joker
24+
Joke() //@Joke,implementations("Joke", ImpJoker)
2925
}
3026

31-
type cryer int
27+
type cryer int //@implementations("cryer", Cryer)
28+
29+
func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry)
3230

33-
func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry")
31+
type Empty interface{} //@implementations("Empty")

internal/lsp/testdata/lsp/primarymod/implementation/other/other.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@ type ImpS struct{} //@mark(OtherImpS, "ImpS")
1010
func (ImpS) Laugh() { //@mark(OtherLaughS, "Laugh")
1111
}
1212

13-
type ImpI interface {
14-
Laugh()
13+
type ImpI interface { //@mark(OtherLaugher, "ImpI")
14+
Laugh() //@mark(OtherLaugh, "Laugh")
1515
}
1616

17-
type Foo struct {
17+
type Foo struct { //@implementations("Foo", Joker)
1818
}
1919

20-
func (Foo) U() { //@mark(ImpU, "U")
20+
func (Foo) Joke() { //@mark(ImpJoker, "Joke"),implementations("Joke", Joke)
2121
}
2222

2323
type CryType int
2424

25-
const Sob CryType = 1
26-
27-
type Cryer interface {
28-
Cry(CryType) //@implementations("Cry", CryImpl)
25+
type Cryer interface { //@Cryer
26+
Cry(CryType) //@Cry,implementations("Cry", CryImpl)
2927
}

internal/lsp/testdata/lsp/summary.txt.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ FuzzyWorkspaceSymbolsCount = 3
2424
CaseSensitiveWorkspaceSymbolsCount = 2
2525
SignaturesCount = 23
2626
LinksCount = 8
27-
ImplementationsCount = 5
27+
ImplementationsCount = 14
2828

0 commit comments

Comments
 (0)