blob: ed8b16383f660cd7272d04cdcb2976ed618d74ef [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"
"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"
)
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
}
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). 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
}