// Copyright 2013 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 main
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/cmd/guru/serial"
|
|
"golang.org/x/tools/go/loader"
|
|
"golang.org/x/tools/go/types/typeutil"
|
|
"golang.org/x/tools/refactor/importgraph"
|
|
)
|
|
|
|
// Implements displays the "implements" relation as it pertains to the
|
|
// selected type.
|
|
// If the selection is a method, 'implements' displays
|
|
// the corresponding methods of the types that would have been reported
|
|
// by an implements query on the receiver type.
|
|
//
|
|
func implements(q *Query) error {
|
|
lconf := loader.Config{Build: q.Build}
|
|
allowErrors(&lconf)
|
|
|
|
qpkg, err := importQueryPackage(q.Pos, &lconf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the packages to search.
|
|
if len(q.Scope) > 0 {
|
|
// Inspect all packages in the analysis scope, if specified.
|
|
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Otherwise inspect the forward and reverse
|
|
// transitive closure of the selected package.
|
|
// (In theory even this is incomplete.)
|
|
_, rev, _ := importgraph.Build(q.Build)
|
|
for path := range rev.Search(qpkg) {
|
|
lconf.ImportWithTests(path)
|
|
}
|
|
|
|
// TODO(adonovan): for completeness, we should also
|
|
// type-check and inspect function bodies in all
|
|
// imported packages. This would be expensive, but we
|
|
// could optimize by skipping functions that do not
|
|
// contain type declarations. This would require
|
|
// changing the loader's TypeCheckFuncBodies hook to
|
|
// provide the []*ast.File.
|
|
}
|
|
|
|
// Load/parse/type-check the program.
|
|
lprog, err := lconf.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the selected type.
|
|
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
|
|
var method *types.Func
|
|
var T types.Type // selected type (receiver if method != nil)
|
|
|
|
switch action {
|
|
case actionExpr:
|
|
// method?
|
|
if id, ok := path[0].(*ast.Ident); ok {
|
|
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
|
recv := obj.Type().(*types.Signature).Recv()
|
|
if recv == nil {
|
|
return fmt.Errorf("this function is not a method")
|
|
}
|
|
method = obj
|
|
T = recv.Type()
|
|
}
|
|
}
|
|
|
|
// If not a method, use the expression's type.
|
|
if T == nil {
|
|
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
|
}
|
|
|
|
case actionType:
|
|
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
|
}
|
|
if T == nil {
|
|
return fmt.Errorf("not a type, method, or value")
|
|
}
|
|
|
|
// 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
|
|
for _, info := range lprog.AllPackages {
|
|
for _, obj := range info.Defs {
|
|
if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
|
|
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
|
|
|
|
// 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 pos interface{} = qpos
|
|
if nt, ok := deref(T).(*types.Named); ok {
|
|
pos = nt.Obj()
|
|
}
|
|
|
|
// Sort types (arbitrarily) to ensure test determinism.
|
|
sort.Sort(typesByString(to))
|
|
sort.Sort(typesByString(from))
|
|
sort.Sort(typesByString(fromPtr))
|
|
|
|
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
|
|
if method != nil {
|
|
for _, t := range to {
|
|
toMethod = append(toMethod,
|
|
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
}
|
|
for _, t := range from {
|
|
fromMethod = append(fromMethod,
|
|
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
}
|
|
for _, t := range fromPtr {
|
|
fromPtrMethod = append(fromPtrMethod,
|
|
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
}
|
|
}
|
|
|
|
q.Output(lprog.Fset, &implementsResult{
|
|
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
type implementsResult struct {
|
|
qpos *queryPos
|
|
|
|
t types.Type // queried type (not necessarily named)
|
|
pos interface{} // pos of t (*types.Name or *QueryPos)
|
|
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
|
|
|
|
// if a method was queried:
|
|
method *types.Func // queried method
|
|
toMethod []*types.Selection // method of type to[i], if any
|
|
fromMethod []*types.Selection // method of type from[i], if any
|
|
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
|
|
}
|
|
|
|
func (r *implementsResult) PrintPlain(printf printfFunc) {
|
|
relation := "is implemented by"
|
|
|
|
meth := func(sel *types.Selection) {
|
|
if sel != nil {
|
|
printf(sel.Obj(), "\t%s method (%s).%s",
|
|
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
|
|
}
|
|
}
|
|
|
|
if isInterface(r.t) {
|
|
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
|
|
printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
|
|
return
|
|
}
|
|
|
|
if r.method == nil {
|
|
printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
|
|
} else {
|
|
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
|
|
}
|
|
|
|
// Show concrete types (or methods) first; use two passes.
|
|
for i, sub := range r.to {
|
|
if !isInterface(sub) {
|
|
if r.method == nil {
|
|
printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
|
|
relation, typeKind(sub), r.qpos.typeString(sub))
|
|
} else {
|
|
meth(r.toMethod[i])
|
|
}
|
|
}
|
|
}
|
|
for i, sub := range r.to {
|
|
if isInterface(sub) {
|
|
if r.method == nil {
|
|
printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
|
|
relation, typeKind(sub), r.qpos.typeString(sub))
|
|
} else {
|
|
meth(r.toMethod[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
relation = "implements"
|
|
for i, super := range r.from {
|
|
if r.method == nil {
|
|
printf(super.(*types.Named).Obj(), "\t%s %s",
|
|
relation, r.qpos.typeString(super))
|
|
} else {
|
|
meth(r.fromMethod[i])
|
|
}
|
|
}
|
|
} else {
|
|
relation = "implements"
|
|
|
|
if r.from != nil {
|
|
if r.method == nil {
|
|
printf(r.pos, "%s type %s",
|
|
typeKind(r.t), r.qpos.typeString(r.t))
|
|
} else {
|
|
printf(r.method, "concrete method %s",
|
|
r.qpos.objectString(r.method))
|
|
}
|
|
for i, super := range r.from {
|
|
if r.method == nil {
|
|
printf(super.(*types.Named).Obj(), "\t%s %s",
|
|
relation, r.qpos.typeString(super))
|
|
} else {
|
|
meth(r.fromMethod[i])
|
|
}
|
|
}
|
|
}
|
|
if r.fromPtr != nil {
|
|
if r.method == nil {
|
|
printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
|
|
} else {
|
|
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
|
printf(r.method, "concrete method %s",
|
|
r.qpos.objectString(r.method))
|
|
}
|
|
|
|
for i, psuper := range r.fromPtr {
|
|
if r.method == nil {
|
|
printf(psuper.(*types.Named).Obj(), "\t%s %s",
|
|
relation, r.qpos.typeString(psuper))
|
|
} else {
|
|
meth(r.fromPtrMethod[i])
|
|
}
|
|
}
|
|
} else if r.from == nil {
|
|
printf(r.pos, "%s type %s implements only interface{}",
|
|
typeKind(r.t), r.qpos.typeString(r.t))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *implementsResult) JSON(fset *token.FileSet) []byte {
|
|
var method *serial.DescribeMethod
|
|
if r.method != nil {
|
|
method = &serial.DescribeMethod{
|
|
Name: r.qpos.objectString(r.method),
|
|
Pos: fset.Position(r.method.Pos()).String(),
|
|
}
|
|
}
|
|
return toJSON(&serial.Implements{
|
|
T: makeImplementsType(r.t, fset),
|
|
AssignableTo: makeImplementsTypes(r.to, fset),
|
|
AssignableFrom: makeImplementsTypes(r.from, fset),
|
|
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
|
|
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
|
|
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
|
|
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
|
|
Method: method,
|
|
})
|
|
|
|
}
|
|
|
|
func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
|
|
var r []serial.ImplementsType
|
|
for _, t := range tt {
|
|
r = append(r, makeImplementsType(t, fset))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
|
|
var pos token.Pos
|
|
if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
|
|
pos = nt.Obj().Pos()
|
|
}
|
|
return serial.ImplementsType{
|
|
Name: T.String(),
|
|
Pos: fset.Position(pos).String(),
|
|
Kind: typeKind(T),
|
|
}
|
|
}
|
|
|
|
// typeKind returns a string describing the underlying kind of type,
|
|
// e.g. "slice", "array", "struct".
|
|
func typeKind(T types.Type) string {
|
|
s := reflect.TypeOf(T.Underlying()).String()
|
|
return strings.ToLower(strings.TrimPrefix(s, "*types."))
|
|
}
|
|
|
|
func isInterface(T types.Type) bool { return types.IsInterface(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] }
|