blob: bcfcc06587de91e9618d8c6acd54601153b54dcf [file] [log] [blame]
// 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"
"errors"
"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) {
if i.Type.Object == nil {
return implementsResult{}, errors.New("no type info object for identifier")
}
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] }