mirror of
https://github.com/golang/go
synced 2024-11-18 21:05:02 -07:00
a27fdba277
We previously only searched for implementations of the object we found in the "widest" package variant. We instead need to search all variants because each variant is type checked separately, and implementations can be located in packages associated with different variants. For example, say you have: -- foo/foo.go -- package foo type Foo int type Fooer interface { Foo() Foo } -- foo/foo_test.go -- package foo func TestFoo(t *testing.T) {} -- bar/bar.go -- package bar import "foo" type impl struct {} func (impl) Foo() foo.Foo { return 0 } When you run find-implementations on the Fooer interface, we previously would start from the (widest) foo.test's Fooer named type. Unfortunately bar imports foo, not foo.test, so bar.impl does not implement foo.test.Fooer. The specific reason is that bar.impl.Foo returns foo.Foo, whereas foo.test.Fooer.Foo returns foo.test.Foo, which are distinct *types.Named objects. Starting our search instead from foo.Fooer resolves this issue. However, we also need to search from foo.test.Fooer so we match any implementations in foo_test.go. Change-Id: I0b0039c98925410751c8f643c8ebd185340e409f Reviewed-on: https://go-review.googlesource.com/c/tools/+/210459 Run-TryBot: Muir Manders <muir@mnd.rs> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The code in this file is based largely on the code in
|
|
// cmd/guru/implements.go. The guru implementation supports
|
|
// looking up "implementers" of methods also, but that
|
|
// code has been cut out here for now for simplicity.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/go/types/typeutil"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/telemetry"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
func (i *IdentifierInfo) Implementation(ctx context.Context) ([]protocol.Location, error) {
|
|
ctx = telemetry.Package.With(ctx, i.pkg.ID())
|
|
|
|
res, err := i.implementations(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var objs []types.Object
|
|
pkgs := map[token.Pos]Package{}
|
|
|
|
if res.toMethod != nil {
|
|
// If we looked up a method, results are in toMethod.
|
|
for _, s := range res.toMethod {
|
|
if pkgs[s.Obj().Pos()] != nil {
|
|
continue
|
|
}
|
|
// Determine package of receiver.
|
|
recv := s.Recv()
|
|
if p, ok := recv.(*types.Pointer); ok {
|
|
recv = p.Elem()
|
|
}
|
|
if n, ok := recv.(*types.Named); ok {
|
|
pkg := res.pkgs[n]
|
|
pkgs[s.Obj().Pos()] = pkg
|
|
}
|
|
// Add object to objs.
|
|
objs = append(objs, s.Obj())
|
|
}
|
|
} else {
|
|
// Otherwise, the results are in to.
|
|
for _, t := range res.to {
|
|
// We'll provide implementations that are named types and pointers to named types.
|
|
if p, ok := t.(*types.Pointer); ok {
|
|
t = p.Elem()
|
|
}
|
|
if n, ok := t.(*types.Named); ok {
|
|
if pkgs[n.Obj().Pos()] != nil {
|
|
continue
|
|
}
|
|
pkg := res.pkgs[n]
|
|
pkgs[n.Obj().Pos()] = pkg
|
|
objs = append(objs, n.Obj())
|
|
}
|
|
}
|
|
}
|
|
|
|
var locations []protocol.Location
|
|
for _, obj := range objs {
|
|
pkg := pkgs[obj.Pos()]
|
|
if pkgs[obj.Pos()] == nil || len(pkg.CompiledGoFiles()) == 0 {
|
|
continue
|
|
}
|
|
file, _, err := i.Snapshot.View().FindPosInPackage(pkgs[obj.Pos()], obj.Pos())
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting file for object", err)
|
|
continue
|
|
}
|
|
ident, err := findIdentifier(i.Snapshot, pkg, file, obj.Pos())
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting ident for object", err)
|
|
continue
|
|
}
|
|
decRange, err := ident.Declaration.Range()
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting range for object", err)
|
|
continue
|
|
}
|
|
// Do not add interface itself to the list.
|
|
if ident.Declaration.spanRange == i.Declaration.spanRange {
|
|
continue
|
|
}
|
|
locations = append(locations, protocol.Location{
|
|
URI: protocol.NewURI(ident.Declaration.URI()),
|
|
Range: decRange,
|
|
})
|
|
}
|
|
return locations, nil
|
|
}
|
|
|
|
var ErrNotAMethod = errors.New("this function is not a method")
|
|
|
|
func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult, error) {
|
|
var T types.Type
|
|
var method *types.Func
|
|
if i.Type.Object == nil {
|
|
// This isn't a type. Is it a method?
|
|
obj, ok := i.Declaration.obj.(*types.Func)
|
|
if !ok {
|
|
return implementsResult{}, fmt.Errorf("no type info object for identifier %q", i.Name)
|
|
}
|
|
recv := obj.Type().(*types.Signature).Recv()
|
|
if recv == nil {
|
|
return implementsResult{}, ErrNotAMethod
|
|
}
|
|
method = obj
|
|
T = recv.Type()
|
|
} else {
|
|
T = i.Type.Object.Type()
|
|
}
|
|
|
|
// Find all named types, even local types (which can have methods
|
|
// due to promotion). We ignore aliases 'type M = N' to avoid
|
|
// duplicate reporting of the Named type N.
|
|
var allNamed []*types.Named
|
|
pkgs := map[*types.Named]Package{}
|
|
for _, pkg := range i.Snapshot.KnownPackages(ctx) {
|
|
info := pkg.GetTypesInfo()
|
|
for _, obj := range info.Defs {
|
|
if obj, ok := obj.(*types.TypeName); ok && !obj.IsAlias() {
|
|
if named, ok := obj.Type().(*types.Named); ok && !isInterface(named) {
|
|
allNamed = append(allNamed, named)
|
|
pkgs[named] = pkg
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var msets typeutil.MethodSetCache
|
|
|
|
// Test each named type.
|
|
var to []types.Type
|
|
for _, U := range allNamed {
|
|
if isInterface(T) {
|
|
if msets.MethodSet(T).Len() == 0 {
|
|
continue // empty interface
|
|
}
|
|
|
|
// T interface, U concrete
|
|
if types.AssignableTo(U, T) {
|
|
to = append(to, U)
|
|
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
|
|
to = append(to, pU)
|
|
}
|
|
}
|
|
}
|
|
var toMethod []*types.Selection // contain nils
|
|
if method != nil {
|
|
for _, t := range to {
|
|
toMethod = append(toMethod,
|
|
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
}
|
|
}
|
|
return implementsResult{pkgs, to, toMethod}, nil
|
|
}
|
|
|
|
// implementsResult contains the results of an implements query.
|
|
type implementsResult struct {
|
|
pkgs map[*types.Named]Package
|
|
to []types.Type // named or ptr-to-named types assignable to interface T
|
|
toMethod []*types.Selection
|
|
}
|