1
0
mirror of https://github.com/golang/go synced 2024-10-01 06:18:31 -06:00
go/internal/lsp/source/implementation.go
Rohan Challa e13f15d1b9 internal/lsp: fixes premature return in find implementations
Find implementations sometimes returns no results, as it prematurely returns when it
finds an invalid object. Instead the behavior should be to check all the objects in case
a later object is a valid interface.

Fixes #35602

Change-Id: I0e3e2aa8d3afeaa34e392c2fe3ef8cdcd13b3d1e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/208959
Run-TryBot: Rohan Challa <rohan@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-26 21:00:16 +00:00

206 lines
5.6 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/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) and the built-in "error".
// 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 {
allNamed = append(allNamed, named)
pkgs[named] = pkg
}
}
}
}
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)
}
}
}
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, from, fromPtr, 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
from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T
toMethod []*types.Selection
}