+++ /dev/null
-// 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"
-)
-
-// The implements function 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] }