|
|
// 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] }
|