mirror of
https://github.com/golang/go
synced 2024-11-18 23:44:43 -07:00
02d0efc0fb
This adds support for the LSP implemention call, based on the guru code for getting implementations. The guru code did much more than we need, so some of the code has been dropped, and other parts of it are ignored (for now). Fixes golang/go#32973 Change-Id: I1a24450e17d5364f25c4b4120be5320b13ac822b Reviewed-on: https://go-review.googlesource.com/c/tools/+/203918 Run-TryBot: Michael Matloob <matloob@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
155 lines
4.3 KiB
Go
155 lines
4.3 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"
|
|
"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 locations []protocol.Location
|
|
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 {
|
|
ph, pkg, err := view.FindFileInPackage(ctx, f.URI(), ident.pkg)
|
|
if err != nil {
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
|
|
return locations, nil
|
|
}
|
|
|
|
func (i *IdentifierInfo) implementations(ctx context.Context) (implementsResult, error) {
|
|
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 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))
|
|
|
|
// TODO(matloob): Perhaps support calling implements on methods instead of just interface types,
|
|
// as guru does.
|
|
|
|
return implementsResult{to, from, fromPtr}, 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
|
|
}
|
|
|
|
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] }
|