internal/lsp: support implements for methods on an interface

This change copies the code in guru's implements implementation
that finds implementations of methods over to gopls, and uses
the information determined to resolve implements requests on
methods. Implements still only works only within packages.

Updates golang/go#32973

Change-Id: I0bd7849a9224fbef7ab8385070b18fbb30703e2b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/206150
Run-TryBot: Michael Matloob <matloob@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Michael Matloob 2019-11-05 17:03:09 -05:00
parent 9f4e794660
commit a99edfee0d
2 changed files with 74 additions and 43 deletions

View File

@ -11,7 +11,7 @@ package source
import ( import (
"context" "context"
"errors" "fmt"
"go/types" "go/types"
"sort" "sort"
@ -31,44 +31,71 @@ func Implementation(ctx context.Context, view View, f File, position protocol.Po
return nil, err return nil, err
} }
var objs []types.Object
if res.toMethod != nil {
// If we looked up a method, results are in toMethod.
for _, s := range res.toMethod {
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 {
objs = append(objs, n.Obj())
}
}
}
var locations []protocol.Location var locations []protocol.Location
for _, t := range res.to { ph, pkg, err := view.FindFileInPackage(ctx, f.URI(), ident.pkg)
// We'll provide implementations that are named types and pointers to named types. if err != nil {
if p, ok := t.(*types.Pointer); ok { return nil, err
t = p.Elem() }
af, _, _, err := ph.Cached()
if err != nil {
return nil, err
}
for _, obj := range objs {
ident, err := findIdentifier(ctx, view.Snapshot(), pkg, af, obj.Pos())
if err != nil {
return nil, err
} }
if n, ok := t.(*types.Named); ok { decRange, err := ident.Declaration.Range()
ph, pkg, err := view.FindFileInPackage(ctx, f.URI(), ident.pkg) if err != nil {
if err != nil { return nil, err
return nil, err
}
f, _, _, err := ph.Cached()
if err != nil {
return nil, err
}
ident, err := findIdentifier(ctx, view.Snapshot(), pkg, f, n.Obj().Pos())
if err != nil {
return nil, err
}
decRange, err := ident.Declaration.Range()
if err != nil {
return nil, err
}
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
})
} }
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
})
} }
return locations, nil return locations, nil
} }
func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult, error) { func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult, error) {
var T types.Type
var method *types.Func
if i.Type.Object == nil { if i.Type.Object == nil {
return implementsResult{}, errors.New("no type info object for identifier") // 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{}, fmt.Errorf("this function is not a method")
}
method = obj
T = recv.Type()
} else {
T = i.Type.Object.Type()
} }
T := i.Type.Object.Type()
// Find all named types, even local types (which can have // Find all named types, even local types (which can have
// methods due to promotion) and the built-in "error". // methods due to promotion) and the built-in "error".
@ -88,7 +115,7 @@ func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult,
var msets typeutil.MethodSetCache var msets typeutil.MethodSetCache
// TODO(matloob): We only use the to result for now. Figure out if we want to // TODO(matloob): We only use the to and toMethod result for now. Figure out if we want to
// surface the from and fromPtr results to users. // surface the from and fromPtr results to users.
// Test each named type. // Test each named type.
var to, from, fromPtr []types.Type var to, from, fromPtr []types.Type
@ -138,17 +165,23 @@ func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult,
sort.Sort(typesByString(from)) sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr)) sort.Sort(typesByString(fromPtr))
// TODO(matloob): Perhaps support calling implements on methods instead of just interface types, var toMethod []*types.Selection // contain nils
// as guru does. if method != nil {
for _, t := range to {
toMethod = append(toMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
}
return implementsResult{to, from, fromPtr}, nil return implementsResult{to, from, fromPtr, toMethod}, nil
} }
// implementsResult contains the results of an implements query. // implementsResult contains the results of an implements query.
type implementsResult struct { type implementsResult struct {
to []types.Type // named or ptr-to-named types assignable to interface T to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T fromPtr []types.Type // named interfaces assignable only from *T
toMethod []*types.Selection
} }
type typesByString []types.Type type typesByString []types.Type

View File

@ -2,20 +2,18 @@ package implementation
type ImpP struct{} //@ImpP type ImpP struct{} //@ImpP
func (*ImpP) Laugh() { func (*ImpP) Laugh() { //@mark(LaughP, "Laugh")
} }
type ImpS struct{} //@ImpS type ImpS struct{} //@ImpS
func (ImpS) Laugh() { func (ImpS) Laugh() { //@mark(LaughS, "Laugh")
} }
type ImpI interface { //@ImpI type ImpI interface { //@ImpI
Laugh() Laugh() //@mark(LaughI, "Laugh"),implementations("augh", LaughP),implementations("augh", LaughS),implementations("augh", LaughL)
} }
type Laugher interface { //@implementations("augher", ImpP),implementations("augher", ImpI),implementations("augher", ImpS), type Laugher interface { //@Laugher,implementations("augher", ImpP),implementations("augher", ImpI),implementations("augher", ImpS),
Laugh() Laugh() //@mark(LaughL, "Laugh"),implementations("augh", LaughP),implementations("augh", LaughI),implementations("augh", LaughS)
} }