Skip to content

Commit 79b5e63

Browse files
committed
gopls/internal/lsp: add go to implementation support for function types
Fixes #56572
1 parent 2b29c66 commit 79b5e63

File tree

5 files changed

+149
-65
lines changed

5 files changed

+149
-65
lines changed

gopls/internal/lsp/source/implementation.go

Lines changed: 122 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp pro
2323
ctx, done := event.Start(ctx, "source.Implementation")
2424
defer done()
2525

26-
impls, err := implementations(ctx, snapshot, f, pp)
26+
impls, err := implementations(ctx, snapshot, f, pp, true)
2727
if err != nil {
2828
return nil, err
2929
}
@@ -58,108 +58,168 @@ func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp pro
5858
var ErrNotAType = errors.New("not a type name or method")
5959

6060
// implementations returns the concrete implementations of the specified
61-
// interface, or the interfaces implemented by the specified concrete type.
62-
// It populates only the definition-related fields of qualifiedObject.
63-
// (Arguably it should return a smaller data type.)
64-
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
61+
// interface, or the interfaces implemented by the specified concrete type,
62+
// or the concrete implementations of a function type. It populates only
63+
// the definition-related fields of qualifiedObject. (Arguably it should
64+
// return a smaller data type.)
65+
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, includeFuncs bool) (impls []qualifiedObject, err error) {
6566
// Find all named types, even local types
6667
// (which can have methods due to promotion).
67-
var (
68-
allNamed []*types.Named
69-
pkgs = make(map[*types.Package]Package)
70-
)
68+
qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
69+
if err != nil {
70+
return nil, err
71+
}
7172
knownPkgs, err := s.KnownPackages(ctx)
7273
if err != nil {
7374
return nil, err
7475
}
76+
pkgs := make(map[*types.Package]Package, len(knownPkgs))
7577
for _, pkg := range knownPkgs {
7678
pkgs[pkg.GetTypes()] = pkg
77-
for _, obj := range pkg.GetTypesInfo().Defs {
78-
obj, ok := obj.(*types.TypeName)
79-
// We ignore aliases 'type M = N' to avoid duplicate reporting
80-
// of the Named type N.
81-
if !ok || obj.IsAlias() {
82-
continue
83-
}
84-
if named, ok := obj.Type().(*types.Named); ok {
85-
allNamed = append(allNamed, named)
79+
}
80+
81+
// Defer collection and caching of named types. It's only required for
82+
// interface definitions, not function type defintions.
83+
var allNamed []*types.Named
84+
getAllNamed := func() []*types.Named {
85+
if allNamed == nil {
86+
for _, pkg := range knownPkgs {
87+
for _, obj := range pkg.GetTypesInfo().Defs {
88+
obj, ok := obj.(*types.TypeName)
89+
// We ignore aliases 'type M = N' to avoid duplicate reporting
90+
// of the Named type N.
91+
if !ok || obj.IsAlias() {
92+
continue
93+
}
94+
if named, ok := obj.Type().(*types.Named); ok {
95+
allNamed = append(allNamed, named)
96+
}
97+
}
8698
}
8799
}
100+
return allNamed
88101
}
89102

90-
qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
91-
if err != nil {
92-
return nil, err
93-
}
94-
var (
95-
impls []qualifiedObject
96-
seen = make(map[token.Position]bool)
97-
)
103+
seen := make(map[token.Position]bool)
98104
for _, qo := range qos {
99-
// Ascertain the query identifier (type or method).
100-
var (
101-
queryType types.Type
102-
queryMethod *types.Func
103-
)
105+
var ok bool
104106
switch obj := qo.obj.(type) {
105107
case *types.Func:
106-
queryMethod = obj
107-
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
108-
queryType = ensurePointer(recv.Type())
108+
recv := obj.Type().(*types.Signature).Recv()
109+
if recv == nil {
110+
break
109111
}
112+
impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(recv.Type()), obj)...)
113+
ok = true
110114
case *types.TypeName:
111-
queryType = ensurePointer(obj.Type())
115+
sig, isFunc := obj.Type().Underlying().(*types.Signature)
116+
if isFunc {
117+
if !includeFuncs {
118+
break
119+
}
120+
impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...)
121+
ok = true
122+
break
123+
}
124+
impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(obj.Type()), nil)...)
125+
ok = true
126+
case *types.Var:
127+
if !includeFuncs {
128+
break
129+
}
130+
sig, isFunc := obj.Type().Underlying().(*types.Signature)
131+
if !isFunc {
132+
break
133+
}
134+
impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...)
135+
ok = true
112136
}
113137

114-
if queryType == nil {
138+
if !ok {
115139
return nil, ErrNotAType
116140
}
141+
}
117142

118-
if types.NewMethodSet(queryType).Len() == 0 {
119-
return nil, nil
143+
return impls, nil
144+
}
145+
146+
func findInterfaceImplementations(pkgs map[*types.Package]Package, getAllNamed func() []*types.Named, seen map[token.Position]bool, s Snapshot, queryType types.Type, queryMethod *types.Func) (impls []qualifiedObject) {
147+
if types.NewMethodSet(queryType).Len() == 0 {
148+
return nil
149+
}
150+
151+
// Find all the named types that match our query.
152+
for _, named := range getAllNamed() {
153+
var (
154+
candObj types.Object = named.Obj()
155+
candType = ensurePointer(named)
156+
)
157+
158+
if !concreteImplementsIntf(candType, queryType) {
159+
continue
120160
}
121161

122-
// Find all the named types that match our query.
123-
for _, named := range allNamed {
124-
var (
125-
candObj types.Object = named.Obj()
126-
candType = ensurePointer(named)
127-
)
162+
ms := types.NewMethodSet(candType)
163+
if ms.Len() == 0 {
164+
// Skip empty interfaces.
165+
continue
166+
}
128167

129-
if !concreteImplementsIntf(candType, queryType) {
168+
// If client queried a method, look up corresponding candType method.
169+
if queryMethod != nil {
170+
sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
171+
if sel == nil {
130172
continue
131173
}
174+
candObj = sel.Obj()
175+
}
176+
177+
pos := s.FileSet().Position(candObj.Pos())
178+
if candObj == queryMethod || seen[pos] {
179+
continue
180+
}
132181

133-
ms := types.NewMethodSet(candType)
134-
if ms.Len() == 0 {
135-
// Skip empty interfaces.
182+
seen[pos] = true
183+
184+
impls = append(impls, qualifiedObject{
185+
obj: candObj,
186+
pkg: pkgs[candObj.Pkg()], // may be nil (e.g. error)
187+
})
188+
}
189+
190+
return impls
191+
}
192+
193+
func findFunctionImplementations(pkgs map[*types.Package]Package, seen map[token.Position]bool, s Snapshot, sig *types.Signature) (impls []qualifiedObject) {
194+
for pkg := range pkgs {
195+
for _, name := range pkg.Scope().Names() {
196+
o := pkg.Scope().Lookup(name)
197+
if _, isType := o.(*types.TypeName); isType {
136198
continue
137199
}
138-
139-
// If client queried a method, look up corresponding candType method.
140-
if queryMethod != nil {
141-
sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
142-
if sel == nil {
143-
continue
144-
}
145-
candObj = sel.Obj()
200+
var csig *types.Signature
201+
var isFunc bool
202+
if csig, isFunc = o.Type().Underlying().(*types.Signature); !isFunc {
203+
continue
146204
}
147205

148-
pos := s.FileSet().Position(candObj.Pos())
149-
if candObj == queryMethod || seen[pos] {
206+
if !types.AssignableTo(sig, csig) {
207+
continue
208+
}
209+
pos := s.FileSet().Position(o.Pos())
210+
if seen[pos] {
150211
continue
151212
}
152213

153214
seen[pos] = true
154215

155216
impls = append(impls, qualifiedObject{
156-
obj: candObj,
157-
pkg: pkgs[candObj.Pkg()], // may be nil (e.g. error)
217+
obj: o,
218+
pkg: pkgs[o.Pkg()], // may be nil (e.g. error)
158219
})
159220
}
160221
}
161-
162-
return impls, nil
222+
return impls
163223
}
164224

165225
// concreteImplementsIntf returns true if a is an interface type implemented by

gopls/internal/lsp/source/references.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func equalOrigin(obj1, obj2 types.Object) bool {
258258
// interfaceReferences returns the references to the interfaces implemented by
259259
// the type or method at the given position.
260260
func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) {
261-
implementations, err := implementations(ctx, s, f, pp)
261+
implementations, err := implementations(ctx, s, f, pp, false)
262262
if err != nil {
263263
if errors.Is(err, ErrNotAType) {
264264
return nil, nil

gopls/internal/lsp/testdata/implementation/implementation.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,27 @@ type cryer int //@implementations("cryer", Cryer)
2929
func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry)
3030

3131
type Empty interface{} //@implementations("Empty")
32+
33+
type FunctionType func(s string, i int) //@FunctionType,implementations("FunctionType", ImplementationOfFunctionType1, ImplementationOfFunctionType2)
34+
35+
func ImplementationOfFunctionType1(s string, i int) { //@mark(ImplementationOfFunctionType1, "ImplementationOfFunctionType1")
36+
}
37+
38+
func ImplementationOfFunctionType2(s string, i int) { //@mark(ImplementationOfFunctionType2, "ImplementationOfFunctionType2")
39+
40+
func TestFunctionType(f FunctionType) {
41+
f("s", 0) //implementations("f", ImplementationOfFunctionType1, ImplementationOfFunctionType2)
42+
}
43+
44+
func implementationOfAnonymous1(data []byte) error { //@mark(implementationOfAnonymous1, "implementationOfAnonymous1")
45+
return nil
46+
}
47+
48+
func implementationOfAnonymous2(data []byte) error { //@mark(implementationOfAnonymous2, "implementationOfAnonymous2")
49+
return nil
50+
}
51+
52+
func TestAnonymousFunction(af func([]byte) error) {
53+
af([]byte{0, 1}) //implementations("af", implementationOfAnonymous1, implementationOfAnonymous2)
54+
}
55+

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ SymbolsCount = 1
2727
WorkspaceSymbolsCount = 20
2828
SignaturesCount = 33
2929
LinksCount = 7
30-
ImplementationsCount = 14
30+
ImplementationsCount = 15
3131

gopls/internal/lsp/testdata/summary_go1.18.txt.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ SymbolsCount = 2
2727
WorkspaceSymbolsCount = 20
2828
SignaturesCount = 33
2929
LinksCount = 7
30-
ImplementationsCount = 14
30+
ImplementationsCount = 15
3131

0 commit comments

Comments
 (0)