From cbd80855995a1031afd45ce67567fa1d736cfa2f Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Fri, 4 Nov 2022 18:21:14 +0000 Subject: [PATCH 1/5] gopls/internal/lsp: add go to implementation support for function types Fixes #56572 --- gopls/internal/lsp/source/implementation.go | 183 ++++++++++++------ gopls/internal/lsp/source/references.go | 2 +- .../testdata/implementation/implementation.go | 24 +++ .../internal/lsp/testdata/summary.txt.golden | 2 +- .../lsp/testdata/summary_go1.18.txt.golden | 2 +- 5 files changed, 149 insertions(+), 64 deletions(-) diff --git a/gopls/internal/lsp/source/implementation.go b/gopls/internal/lsp/source/implementation.go index ca62f4e664d..feb1e1420fc 100644 --- a/gopls/internal/lsp/source/implementation.go +++ b/gopls/internal/lsp/source/implementation.go @@ -23,7 +23,7 @@ func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp pro ctx, done := event.Start(ctx, "source.Implementation") defer done() - impls, err := implementations(ctx, snapshot, f, pp) + impls, err := implementations(ctx, snapshot, f, pp, true) if err != nil { return nil, err } @@ -58,94 +58,156 @@ func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp pro 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. -// It populates only the definition-related fields of qualifiedObject. -// (Arguably it should return a smaller data type.) -func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) { +// interface, or the interfaces implemented by the specified concrete type, +// or the concrete implementations of a function type. It populates only +// the definition-related fields of qualifiedObject. (Arguably it should +// return a smaller data type.) +func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, includeFuncs bool) (impls []qualifiedObject, err error) { // Find all named types, even local types // (which can have methods due to promotion). - var ( - allNamed []*types.Named - pkgs = make(map[*types.Package]Package) - ) + qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp) + if err != nil { + return nil, err + } knownPkgs, err := s.KnownPackages(ctx) if err != nil { return nil, err } + pkgs := make(map[*types.Package]Package, len(knownPkgs)) for _, pkg := range knownPkgs { pkgs[pkg.GetTypes()] = pkg - for _, obj := range pkg.GetTypesInfo().Defs { - obj, ok := obj.(*types.TypeName) - // We ignore aliases 'type M = N' to avoid duplicate reporting - // of the Named type N. - if !ok || obj.IsAlias() { - continue - } - if named, ok := obj.Type().(*types.Named); ok { - allNamed = append(allNamed, named) + } + + // Defer collection and caching of named types. It's only required for + // interface definitions, not function type defintions. + var allNamed []*types.Named + getAllNamed := func() []*types.Named { + if allNamed == nil { + for _, pkg := range knownPkgs { + for _, obj := range pkg.GetTypesInfo().Defs { + obj, ok := obj.(*types.TypeName) + // We ignore aliases 'type M = N' to avoid duplicate reporting + // of the Named type N. + if !ok || obj.IsAlias() { + continue + } + if named, ok := obj.Type().(*types.Named); ok { + allNamed = append(allNamed, named) + } + } } } + return allNamed } - qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp) - if err != nil { - return nil, err - } - var ( - impls []qualifiedObject - seen = make(map[token.Position]bool) - ) + seen := make(map[token.Position]bool) for _, qo := range qos { - // Ascertain the query identifier (type or method). - var ( - queryType types.Type - queryMethod *types.Func - ) + var ok bool switch obj := qo.obj.(type) { case *types.Func: - queryMethod = obj - if recv := obj.Type().(*types.Signature).Recv(); recv != nil { - queryType = ensurePointer(recv.Type()) + recv := obj.Type().(*types.Signature).Recv() + if recv == nil { + break } + impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(recv.Type()), obj)...) + ok = true case *types.TypeName: - queryType = ensurePointer(obj.Type()) + sig, isFunc := obj.Type().Underlying().(*types.Signature) + if isFunc { + if !includeFuncs { + break + } + impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...) + ok = true + break + } + impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(obj.Type()), nil)...) + ok = true + case *types.Var: + if !includeFuncs { + break + } + sig, isFunc := obj.Type().Underlying().(*types.Signature) + if !isFunc { + break + } + impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...) + ok = true } - if queryType == nil { + if !ok { return nil, ErrNotAType } + } - if types.NewMethodSet(queryType).Len() == 0 { - return nil, nil + return impls, nil +} + +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) { + if types.NewMethodSet(queryType).Len() == 0 { + return nil + } + + // Find all the named types that match our query. + for _, named := range getAllNamed() { + var ( + candObj types.Object = named.Obj() + candType = ensurePointer(named) + ) + + if !concreteImplementsIntf(candType, queryType) { + continue } - // Find all the named types that match our query. - for _, named := range allNamed { - var ( - candObj types.Object = named.Obj() - candType = ensurePointer(named) - ) + ms := types.NewMethodSet(candType) + if ms.Len() == 0 { + // Skip empty interfaces. + continue + } - if !concreteImplementsIntf(candType, queryType) { + // 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() + } + + pos := s.FileSet().Position(candObj.Pos()) + if candObj == queryMethod || seen[pos] { + continue + } - ms := types.NewMethodSet(candType) - if ms.Len() == 0 { - // Skip empty interfaces. + seen[pos] = true + + impls = append(impls, qualifiedObject{ + obj: candObj, + pkg: pkgs[candObj.Pkg()], // may be nil (e.g. error) + }) + } + + return impls +} + +func findFunctionImplementations(pkgs map[*types.Package]Package, seen map[token.Position]bool, s Snapshot, sig *types.Signature) (impls []qualifiedObject) { + for pkg := range pkgs { + for _, name := range pkg.Scope().Names() { + o := pkg.Scope().Lookup(name) + if _, isType := o.(*types.TypeName); isType { 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 csig *types.Signature + var isFunc bool + if csig, isFunc = o.Type().Underlying().(*types.Signature); !isFunc { + continue } - if candObj == queryMethod { + if !types.AssignableTo(sig, csig) { + continue + } + pos := s.FileSet().Position(o.Pos()) + if seen[pos] { continue } @@ -165,13 +227,12 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol. seen[posn] = true impls = append(impls, qualifiedObject{ - obj: candObj, - pkg: pkg, + obj: o, + pkg: pkgs[o.Pkg()], // may be nil (e.g. error) }) } } - - return impls, nil + return impls } // concreteImplementsIntf returns true if a is an interface type implemented by diff --git a/gopls/internal/lsp/source/references.go b/gopls/internal/lsp/source/references.go index d0310560c10..0a219070dfd 100644 --- a/gopls/internal/lsp/source/references.go +++ b/gopls/internal/lsp/source/references.go @@ -258,7 +258,7 @@ func equalOrigin(obj1, obj2 types.Object) bool { // interfaceReferences returns the references to the interfaces implemented by // the type or method at the given position. func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) { - implementations, err := implementations(ctx, s, f, pp) + implementations, err := implementations(ctx, s, f, pp, false) if err != nil { if errors.Is(err, ErrNotAType) { return nil, nil diff --git a/gopls/internal/lsp/testdata/implementation/implementation.go b/gopls/internal/lsp/testdata/implementation/implementation.go index b817319d5ef..faf49847785 100644 --- a/gopls/internal/lsp/testdata/implementation/implementation.go +++ b/gopls/internal/lsp/testdata/implementation/implementation.go @@ -29,3 +29,27 @@ type cryer int //@implementations("cryer", Cryer) func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry) type Empty interface{} //@implementations("Empty") + +type FunctionType func(s string, i int) //@FunctionType,implementations("FunctionType", ImplementationOfFunctionType1, ImplementationOfFunctionType2) + +func ImplementationOfFunctionType1(s string, i int) { //@mark(ImplementationOfFunctionType1, "ImplementationOfFunctionType1") +} + +func ImplementationOfFunctionType2(s string, i int) { //@mark(ImplementationOfFunctionType2, "ImplementationOfFunctionType2") + +func TestFunctionType(f FunctionType) { + f("s", 0) //implementations("f", ImplementationOfFunctionType1, ImplementationOfFunctionType2) +} + +func implementationOfAnonymous1(data []byte) error { //@mark(implementationOfAnonymous1, "implementationOfAnonymous1") + return nil +} + +func implementationOfAnonymous2(data []byte) error { //@mark(implementationOfAnonymous2, "implementationOfAnonymous2") + return nil +} + +func TestAnonymousFunction(af func([]byte) error) { + af([]byte{0, 1}) //implementations("af", implementationOfAnonymous1, implementationOfAnonymous2) +} + diff --git a/gopls/internal/lsp/testdata/summary.txt.golden b/gopls/internal/lsp/testdata/summary.txt.golden index cfe8e4a267d..5dc731c28ff 100644 --- a/gopls/internal/lsp/testdata/summary.txt.golden +++ b/gopls/internal/lsp/testdata/summary.txt.golden @@ -27,5 +27,5 @@ SymbolsCount = 1 WorkspaceSymbolsCount = 20 SignaturesCount = 33 LinksCount = 7 -ImplementationsCount = 14 +ImplementationsCount = 15 diff --git a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden index 2b7bf976b2f..b7ec9006fc6 100644 --- a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden +++ b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden @@ -27,5 +27,5 @@ SymbolsCount = 2 WorkspaceSymbolsCount = 20 SignaturesCount = 33 LinksCount = 7 -ImplementationsCount = 14 +ImplementationsCount = 15 From 513214e18196979356f2fc0f0a4e0407690cc23b Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Mon, 7 Nov 2022 14:39:30 +0000 Subject: [PATCH 2/5] gopls/internal/lsp: add go to implementation support for function types Refactor, taking into account initial code review comments. Fixes #56572 --- gopls/internal/lsp/source/implementation.go | 101 +++++++----------- .../testdata/implementation/implementation.go | 8 ++ 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/gopls/internal/lsp/source/implementation.go b/gopls/internal/lsp/source/implementation.go index feb1e1420fc..9895e128c55 100644 --- a/gopls/internal/lsp/source/implementation.go +++ b/gopls/internal/lsp/source/implementation.go @@ -100,50 +100,60 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol. return allNamed } - seen := make(map[token.Position]bool) + var objs []types.Object + for _, qo := range qos { - var ok bool + var sig *types.Signature + var iface types.Type + var method *types.Func + switch obj := qo.obj.(type) { case *types.Func: recv := obj.Type().(*types.Signature).Recv() if recv == nil { break } - impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(recv.Type()), obj)...) - ok = true + iface = ensurePointer(recv.Type()) + method = obj case *types.TypeName: - sig, isFunc := obj.Type().Underlying().(*types.Signature) - if isFunc { - if !includeFuncs { - break - } - impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...) - ok = true + sig, _ = obj.Type().Underlying().(*types.Signature) + if sig != nil { break } - impls = append(impls, findInterfaceImplementations(pkgs, getAllNamed, seen, s, ensurePointer(obj.Type()), nil)...) - ok = true + iface = ensurePointer(obj.Type()) case *types.Var: - if !includeFuncs { - break - } - sig, isFunc := obj.Type().Underlying().(*types.Signature) - if !isFunc { - break - } - impls = append(impls, findFunctionImplementations(pkgs, seen, s, sig)...) - ok = true + sig, _ = obj.Type().Underlying().(*types.Signature) } - if !ok { - return nil, ErrNotAType + if iface != nil { + objs = append(objs, findInterfaceImplementations(getAllNamed, s, iface, method)...) + continue + } + if sig != nil && includeFuncs { + objs = append(objs, findFunctionImplementations(pkgs, s, sig)...) + continue + } + + return nil, ErrNotAType + } + + seen := make(map[token.Position]bool) + for _, obj := range objs { + pos := s.FileSet().Position(obj.Pos()) + if seen[pos] { + continue } + seen[pos] = true + impls = append(impls, qualifiedObject{ + obj: obj, + pkg: pkgs[obj.Pkg()], // may be nil (e.g. error) + }) } return impls, nil } -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) { +func findInterfaceImplementations(getAllNamed func() []*types.Named, s Snapshot, queryType types.Type, queryMethod *types.Func) (objs []types.Object) { if types.NewMethodSet(queryType).Len() == 0 { return nil } @@ -174,23 +184,16 @@ func findInterfaceImplementations(pkgs map[*types.Package]Package, getAllNamed f candObj = sel.Obj() } - pos := s.FileSet().Position(candObj.Pos()) - if candObj == queryMethod || seen[pos] { + if candObj == queryMethod { continue } - seen[pos] = true - - impls = append(impls, qualifiedObject{ - obj: candObj, - pkg: pkgs[candObj.Pkg()], // may be nil (e.g. error) - }) + objs = append(objs, candObj) } - - return impls + return objs } -func findFunctionImplementations(pkgs map[*types.Package]Package, seen map[token.Position]bool, s Snapshot, sig *types.Signature) (impls []qualifiedObject) { +func findFunctionImplementations(pkgs map[*types.Package]Package, s Snapshot, sig *types.Signature) (objs []types.Object) { for pkg := range pkgs { for _, name := range pkg.Scope().Names() { o := pkg.Scope().Lookup(name) @@ -206,33 +209,11 @@ func findFunctionImplementations(pkgs map[*types.Package]Package, seen map[token if !types.AssignableTo(sig, csig) { continue } - pos := s.FileSet().Position(o.Pos()) - if seen[pos] { - continue - } - - pkg := pkgs[candObj.Pkg()] // may be nil (e.g. error) - - // TODO(adonovan): the logic below assumes there is only one - // predeclared (pkg=nil) object of interest, the error type. - // That could change in a future version of Go. - - var posn token.Position - if pkg != nil { - posn = pkg.FileSet().Position(candObj.Pos()) - } - if seen[posn] { - continue - } - seen[posn] = true - impls = append(impls, qualifiedObject{ - obj: o, - pkg: pkgs[o.Pkg()], // may be nil (e.g. error) - }) + objs = append(objs, o) } } - return impls + return objs } // concreteImplementsIntf returns true if a is an interface type implemented by diff --git a/gopls/internal/lsp/testdata/implementation/implementation.go b/gopls/internal/lsp/testdata/implementation/implementation.go index faf49847785..3e13b51f19b 100644 --- a/gopls/internal/lsp/testdata/implementation/implementation.go +++ b/gopls/internal/lsp/testdata/implementation/implementation.go @@ -41,6 +41,14 @@ func TestFunctionType(f FunctionType) { f("s", 0) //implementations("f", ImplementationOfFunctionType1, ImplementationOfFunctionType2) } +type StructWithFunctionFields struct { + FT FunctionType //implementations("FT", ImplementationOfFunctionType1, ImplementationOfFunctionType2) +} + +func (s StructWithFunctionFields) struct { + s.FT("s", 0) //implementations("FT", ImplementationOfFunctionType1, ImplementationOfFunctionType2) +} + func implementationOfAnonymous1(data []byte) error { //@mark(implementationOfAnonymous1, "implementationOfAnonymous1") return nil } From 07c76b8a5ccc26c2071f1ae2854432aeb9cc32b5 Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Mon, 7 Nov 2022 14:56:25 +0000 Subject: [PATCH 3/5] gopls/internal/lsp: add go to implementation support for function types Improve test. Fixes #56572 --- gopls/internal/lsp/testdata/implementation/implementation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gopls/internal/lsp/testdata/implementation/implementation.go b/gopls/internal/lsp/testdata/implementation/implementation.go index 3e13b51f19b..82526c6f00b 100644 --- a/gopls/internal/lsp/testdata/implementation/implementation.go +++ b/gopls/internal/lsp/testdata/implementation/implementation.go @@ -45,7 +45,7 @@ type StructWithFunctionFields struct { FT FunctionType //implementations("FT", ImplementationOfFunctionType1, ImplementationOfFunctionType2) } -func (s StructWithFunctionFields) struct { +func (s StructWithFunctionFields) Test() { s.FT("s", 0) //implementations("FT", ImplementationOfFunctionType1, ImplementationOfFunctionType2) } From 34e4ae7576e7c927cb2da1d76d3946fc25547d91 Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Sat, 12 Nov 2022 21:35:09 +0000 Subject: [PATCH 4/5] gopls/internal/lsp: update implementation to simplify loop body --- gopls/internal/lsp/source/implementation.go | 143 +++++++++--------- .../testdata/implementation/implementation.go | 4 +- 2 files changed, 72 insertions(+), 75 deletions(-) diff --git a/gopls/internal/lsp/source/implementation.go b/gopls/internal/lsp/source/implementation.go index 9895e128c55..945723fb880 100644 --- a/gopls/internal/lsp/source/implementation.go +++ b/gopls/internal/lsp/source/implementation.go @@ -73,70 +73,47 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol. if err != nil { return nil, err } - pkgs := make(map[*types.Package]Package, len(knownPkgs)) - for _, pkg := range knownPkgs { - pkgs[pkg.GetTypes()] = pkg - } - - // Defer collection and caching of named types. It's only required for - // interface definitions, not function type defintions. - var allNamed []*types.Named - getAllNamed := func() []*types.Named { - if allNamed == nil { - for _, pkg := range knownPkgs { - for _, obj := range pkg.GetTypesInfo().Defs { - obj, ok := obj.(*types.TypeName) - // We ignore aliases 'type M = N' to avoid duplicate reporting - // of the Named type N. - if !ok || obj.IsAlias() { - continue - } - if named, ok := obj.Type().(*types.Named); ok { - allNamed = append(allNamed, named) - } - } - } - } - return allNamed - } var objs []types.Object for _, qo := range qos { - var sig *types.Signature - var iface types.Type - var method *types.Func - - switch obj := qo.obj.(type) { - case *types.Func: - recv := obj.Type().(*types.Signature).Recv() - if recv == nil { - break - } - iface = ensurePointer(recv.Type()) - method = obj - case *types.TypeName: - sig, _ = obj.Type().Underlying().(*types.Signature) - if sig != nil { - break - } - iface = ensurePointer(obj.Type()) - case *types.Var: - sig, _ = obj.Type().Underlying().(*types.Signature) + var queryType types.Type + var queryMethod types.Object + + sig, hasSig := qo.obj.Type().Underlying().(*types.Signature) + // If there's a signature, then qo must be a function. + // If there's no receiver, then search for implementations of the + // function type, or function types that qo's signature matches. + if hasSig && sig.Recv() != nil { + // If there's a receiver, then qo must be a method. + // Query for implementations of the interface's method / + // interfaces that the method fully or partially implements. + queryType = ensurePointer(sig.Recv().Type()) + queryMethod = qo.obj + } else if !hasSig { + // If there's no signature, then qo must be a type. + // Query for implementations of the interface / types that + // implement the interface. + queryType = ensurePointer(qo.obj.Type()) } - if iface != nil { - objs = append(objs, findInterfaceImplementations(getAllNamed, s, iface, method)...) - continue - } - if sig != nil && includeFuncs { - objs = append(objs, findFunctionImplementations(pkgs, s, sig)...) - continue + if queryType != nil { + for _, pkg := range knownPkgs { + objs = append(objs, findInterfaceImplementations(pkg, queryType, queryMethod)...) + } + } else if hasSig && includeFuncs { + for _, pkg := range knownPkgs { + objs = append(objs, findFunctionImplementations(pkg, sig)...) + } + } else { + return nil, ErrNotAType } - - return nil, ErrNotAType } + pkgs := make(map[*types.Package]Package, len(knownPkgs)) + for _, pkg := range knownPkgs { + pkgs[pkg.GetTypes()] = pkg + } seen := make(map[token.Position]bool) for _, obj := range objs { pos := s.FileSet().Position(obj.Pos()) @@ -153,13 +130,23 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol. return impls, nil } -func findInterfaceImplementations(getAllNamed func() []*types.Named, s Snapshot, queryType types.Type, queryMethod *types.Func) (objs []types.Object) { +func findInterfaceImplementations(pkg Package, queryType types.Type, queryMethod types.Object) (objs []types.Object) { if types.NewMethodSet(queryType).Len() == 0 { return nil } + for _, obj := range pkg.GetTypesInfo().Defs { + obj, ok := obj.(*types.TypeName) + // We ignore aliases 'type M = N' to avoid duplicate reporting + // of the Named type N. + if !ok || obj.IsAlias() { + continue + } + named, ok := obj.Type().(*types.Named) + if !ok { + continue + } - // Find all the named types that match our query. - for _, named := range getAllNamed() { + // Find all the named types that match our query. var ( candObj types.Object = named.Obj() candType = ensurePointer(named) @@ -193,29 +180,39 @@ func findInterfaceImplementations(getAllNamed func() []*types.Named, s Snapshot, return objs } -func findFunctionImplementations(pkgs map[*types.Package]Package, s Snapshot, sig *types.Signature) (objs []types.Object) { - for pkg := range pkgs { - for _, name := range pkg.Scope().Names() { - o := pkg.Scope().Lookup(name) - if _, isType := o.(*types.TypeName); isType { - continue - } - var csig *types.Signature - var isFunc bool - if csig, isFunc = o.Type().Underlying().(*types.Signature); !isFunc { - continue - } - - if !types.AssignableTo(sig, csig) { - continue +func findFunctionImplementations(pkg Package, sig *types.Signature) (objs []types.Object) { + for _, name := range pkg.GetTypes().Scope().Names() { + o := pkg.GetTypes().Scope().Lookup(name) + + // Look up methods that match the signature. + if obj, isTypeName := o.(*types.TypeName); isTypeName && !obj.IsAlias() { + if named, isNamed := obj.Type().(*types.Named); isNamed { + ms := types.NewMethodSet(ensurePointer(named)) + for i := 0; i < ms.Len(); i++ { + o := ms.At(i).Obj() + if objectImplementsSignature(o, sig) { + objs = append(objs, o) + } + } } + } + // Look up functions that match. + if _, isType := o.(*types.TypeName); isType { + continue + } + if objectImplementsSignature(o, sig) { objs = append(objs, o) } } return objs } +func objectImplementsSignature(o types.Object, sig *types.Signature) bool { + csig, isSig := o.Type().Underlying().(*types.Signature) + return isSig && types.AssignableTo(sig, csig) +} + // concreteImplementsIntf returns true if a is an interface type implemented by // concrete type b, or vice versa. func concreteImplementsIntf(a, b types.Type) bool { diff --git a/gopls/internal/lsp/testdata/implementation/implementation.go b/gopls/internal/lsp/testdata/implementation/implementation.go index 82526c6f00b..ffdff3b322a 100644 --- a/gopls/internal/lsp/testdata/implementation/implementation.go +++ b/gopls/internal/lsp/testdata/implementation/implementation.go @@ -57,7 +57,7 @@ func implementationOfAnonymous2(data []byte) error { //@mark(implementationOfAno return nil } -func TestAnonymousFunction(af func([]byte) error) { +func TestAnonymousFunction(af func([]byte, cry func(other.CryType)) error) { af([]byte{0, 1}) //implementations("af", implementationOfAnonymous1, implementationOfAnonymous2) + cry(other.CryType(12)) //implementations("Cry", Cry) } - From 1dc58ee146ec697e886d93e975e9d159b1e4a22c Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Fri, 4 Nov 2022 18:21:14 +0000 Subject: [PATCH 5/5] gopls/internal/lsp: add go to implementation support for function types Fixes #56572 fix: add null check from commit 13648cdeaf9ceb853cfba57e63c7ae5cfe4e7b7d --- gopls/internal/lsp/source/implementation.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gopls/internal/lsp/source/implementation.go b/gopls/internal/lsp/source/implementation.go index 945723fb880..0d60841ac4a 100644 --- a/gopls/internal/lsp/source/implementation.go +++ b/gopls/internal/lsp/source/implementation.go @@ -116,14 +116,23 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol. } seen := make(map[token.Position]bool) for _, obj := range objs { - pos := s.FileSet().Position(obj.Pos()) - if seen[pos] { + pkg := pkgs[obj.Pkg()] // may be nil (e.g. error) + + // TODO(adonovan): the logic below assumes there is only one + // predeclared (pkg=nil) object of interest, the error type. + // That could change in a future version of Go. + + var posn token.Position + if pkg != nil { + posn = pkg.FileSet().Position(obj.Pos()) + } + if seen[posn] { continue } - seen[pos] = true + seen[posn] = true impls = append(impls, qualifiedObject{ obj: obj, - pkg: pkgs[obj.Pkg()], // may be nil (e.g. error) + pkg: pkg, }) }