1
0
mirror of https://github.com/golang/go synced 2024-11-18 23:34:45 -07:00
go/internal/lsp/source/implementation.go
Michael Matloob a99edfee0d 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>
2019-11-11 15:43:54 +00:00

192 lines
5.1 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/types"
"sort"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/lsp/protocol"
)
func Implementation(ctx context.Context, view View, f File, position protocol.Position) ([]protocol.Location, error) {
// Find all references to the identifier at the position.
ident, err := Identifier(ctx, view, f, position)
if err != nil {
return nil, err
}
res, err := ident.implementations(ctx)
if err != nil {
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
ph, pkg, err := view.FindFileInPackage(ctx, f.URI(), ident.pkg)
if err != nil {
return nil, err
}
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
}
decRange, err := ident.Declaration.Range()
if err != nil {
return nil, err
}
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
})
}
return locations, nil
}
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{}, fmt.Errorf("this function is not a method")
}
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) and the built-in "error".
// We ignore aliases 'type M = N' to avoid duplicate
// reporting of the Named type N.
var allNamed []*types.Named
info := i.pkg.GetTypesInfo()
for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && !obj.IsAlias() {
if named, ok := obj.Type().(*types.Named); ok {
allNamed = append(allNamed, named)
}
}
}
allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
var msets typeutil.MethodSetCache
// 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.
// Test each named type.
var to, from, fromPtr []types.Type
for _, U := range allNamed {
if isInterface(T) {
if msets.MethodSet(T).Len() == 0 {
continue // empty interface
}
if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}
// T interface, U interface
if !types.Identical(T, U) {
if types.AssignableTo(U, T) {
to = append(to, U)
}
if types.AssignableTo(T, U) {
from = append(from, U)
}
}
} else {
// 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)
}
}
} else if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}
// T concrete, U interface
if types.AssignableTo(T, U) {
from = append(from, U)
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
fromPtr = append(fromPtr, U)
}
}
}
// Sort types (arbitrarily) to ensure test determinism.
sort.Sort(typesByString(to))
sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr))
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{to, from, fromPtr, toMethod}, nil
}
// implementsResult contains the results of an implements query.
type implementsResult struct {
to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T
toMethod []*types.Selection
}
type typesByString []types.Type
func (p typesByString) Len() int { return len(p) }
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }