mirror of
https://github.com/golang/go
synced 2024-11-19 00:04:40 -07:00
a6aac22fcd
We weren't returning promoted methods as implementations when the promoted method was defined in a different package than the type implementing the interface. Fix by properly mapping the implementer types.Object to its containing source.Package. I generalized the implementations() result to just contain the implementer objects and their containing package. This allowed me to get rid of some result prep code in Implementation(). Fixes golang/go#35972. Change-Id: I867f2114c34e2ad39515ee3c8b6354c1cd35f7af Reviewed-on: https://go-review.googlesource.com/c/tools/+/210280 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
149 lines
3.5 KiB
Go
149 lines
3.5 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.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/telemetry"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
func (i *IdentifierInfo) Implementation(ctx context.Context) ([]protocol.Location, error) {
|
|
ctx = telemetry.Package.With(ctx, i.pkg.ID())
|
|
|
|
impls, err := i.implementations(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var locations []protocol.Location
|
|
for _, impl := range impls {
|
|
if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
|
|
continue
|
|
}
|
|
|
|
file, _, err := i.Snapshot.View().FindPosInPackage(impl.pkg, impl.obj.Pos())
|
|
if err != nil {
|
|
log.Error(ctx, "Error getting file for object", err)
|
|
continue
|
|
}
|
|
|
|
ident, err := findIdentifier(i.Snapshot, impl.pkg, file, impl.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
|
|
}
|
|
|
|
locations = append(locations, protocol.Location{
|
|
URI: protocol.NewURI(ident.Declaration.URI()),
|
|
Range: decRange,
|
|
})
|
|
}
|
|
return locations, nil
|
|
}
|
|
|
|
var ErrNotAnInterface = errors.New("not an interface or interface method")
|
|
|
|
func (i *IdentifierInfo) implementations(ctx context.Context) ([]implementation, error) {
|
|
var (
|
|
T *types.Interface
|
|
method *types.Func
|
|
)
|
|
|
|
switch obj := i.Declaration.obj.(type) {
|
|
case *types.Func:
|
|
method = obj
|
|
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
|
T, _ = recv.Type().Underlying().(*types.Interface)
|
|
}
|
|
case *types.TypeName:
|
|
T, _ = obj.Type().Underlying().(*types.Interface)
|
|
}
|
|
|
|
if T == nil {
|
|
return nil, ErrNotAnInterface
|
|
}
|
|
|
|
if T.NumMethods() == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Find all named types, even local types (which can have methods
|
|
// due to promotion).
|
|
var (
|
|
allNamed []*types.Named
|
|
pkgs = make(map[*types.Package]Package)
|
|
)
|
|
for _, pkg := range i.Snapshot.KnownPackages(ctx) {
|
|
pkgs[pkg.GetTypes()] = pkg
|
|
|
|
info := pkg.GetTypesInfo()
|
|
for _, obj := range info.Defs {
|
|
// We ignore aliases 'type M = N' to avoid duplicate reporting
|
|
// of the Named type N.
|
|
if obj, ok := obj.(*types.TypeName); ok && !obj.IsAlias() {
|
|
// We skip interface types since we only want concrete
|
|
// implementations.
|
|
if named, ok := obj.Type().(*types.Named); ok && !isInterface(named) {
|
|
allNamed = append(allNamed, named)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
impls []implementation
|
|
seen = make(map[types.Object]bool)
|
|
)
|
|
|
|
// Find all the named types that implement our interface.
|
|
for _, U := range allNamed {
|
|
var concrete types.Type = U
|
|
if !types.AssignableTo(concrete, T) {
|
|
// We also accept T if *T implements our interface.
|
|
concrete = types.NewPointer(concrete)
|
|
if !types.AssignableTo(concrete, T) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var obj types.Object = U.Obj()
|
|
if method != nil {
|
|
obj = types.NewMethodSet(concrete).Lookup(method.Pkg(), method.Name()).Obj()
|
|
}
|
|
|
|
if obj == method || seen[obj] {
|
|
continue
|
|
}
|
|
|
|
seen[obj] = true
|
|
|
|
impls = append(impls, implementation{
|
|
obj: obj,
|
|
pkg: pkgs[obj.Pkg()],
|
|
})
|
|
}
|
|
|
|
return impls, nil
|
|
}
|
|
|
|
type implementation struct {
|
|
// obj is the implementation, either a *types.TypeName or *types.Func.
|
|
obj types.Object
|
|
|
|
// pkg is the Package that contains obj's definition.
|
|
pkg Package
|
|
}
|