github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/implements.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  	"reflect"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/april1989/origin-go-tools/cmd/guru/serial"
    17  	"github.com/april1989/origin-go-tools/go/loader"
    18  	"github.com/april1989/origin-go-tools/go/types/typeutil"
    19  	"github.com/april1989/origin-go-tools/refactor/importgraph"
    20  )
    21  
    22  // The implements function displays the "implements" relation as it pertains to the
    23  // selected type.
    24  // If the selection is a method, 'implements' displays
    25  // the corresponding methods of the types that would have been reported
    26  // by an implements query on the receiver type.
    27  //
    28  func implements(q *Query) error {
    29  	lconf := loader.Config{Build: q.Build}
    30  	allowErrors(&lconf)
    31  
    32  	qpkg, err := importQueryPackage(q.Pos, &lconf)
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	// Set the packages to search.
    38  	if len(q.Scope) > 0 {
    39  		// Inspect all packages in the analysis scope, if specified.
    40  		if err := setPTAScope(&lconf, q.Scope); err != nil {
    41  			return err
    42  		}
    43  	} else {
    44  		// Otherwise inspect the forward and reverse
    45  		// transitive closure of the selected package.
    46  		// (In theory even this is incomplete.)
    47  		_, rev, _ := importgraph.Build(q.Build)
    48  		for path := range rev.Search(qpkg) {
    49  			lconf.ImportWithTests(path)
    50  		}
    51  
    52  		// TODO(adonovan): for completeness, we should also
    53  		// type-check and inspect function bodies in all
    54  		// imported packages.  This would be expensive, but we
    55  		// could optimize by skipping functions that do not
    56  		// contain type declarations.  This would require
    57  		// changing the loader's TypeCheckFuncBodies hook to
    58  		// provide the []*ast.File.
    59  	}
    60  
    61  	// Load/parse/type-check the program.
    62  	lprog, err := lconf.Load()
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	qpos, err := parseQueryPos(lprog, q.Pos, false)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	// Find the selected type.
    73  	path, action := findInterestingNode(qpos.info, qpos.path)
    74  
    75  	var method *types.Func
    76  	var T types.Type // selected type (receiver if method != nil)
    77  
    78  	switch action {
    79  	case actionExpr:
    80  		// method?
    81  		if id, ok := path[0].(*ast.Ident); ok {
    82  			if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
    83  				recv := obj.Type().(*types.Signature).Recv()
    84  				if recv == nil {
    85  					return fmt.Errorf("this function is not a method")
    86  				}
    87  				method = obj
    88  				T = recv.Type()
    89  			}
    90  		}
    91  
    92  		// If not a method, use the expression's type.
    93  		if T == nil {
    94  			T = qpos.info.TypeOf(path[0].(ast.Expr))
    95  		}
    96  
    97  	case actionType:
    98  		T = qpos.info.TypeOf(path[0].(ast.Expr))
    99  	}
   100  	if T == nil {
   101  		return fmt.Errorf("not a type, method, or value")
   102  	}
   103  
   104  	// Find all named types, even local types (which can have
   105  	// methods due to promotion) and the built-in "error".
   106  	// We ignore aliases 'type M = N' to avoid duplicate
   107  	// reporting of the Named type N.
   108  	var allNamed []*types.Named
   109  	for _, info := range lprog.AllPackages {
   110  		for _, obj := range info.Defs {
   111  			if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
   112  				if named, ok := obj.Type().(*types.Named); ok {
   113  					allNamed = append(allNamed, named)
   114  				}
   115  			}
   116  		}
   117  	}
   118  	allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
   119  
   120  	var msets typeutil.MethodSetCache
   121  
   122  	// Test each named type.
   123  	var to, from, fromPtr []types.Type
   124  	for _, U := range allNamed {
   125  		if isInterface(T) {
   126  			if msets.MethodSet(T).Len() == 0 {
   127  				continue // empty interface
   128  			}
   129  			if isInterface(U) {
   130  				if msets.MethodSet(U).Len() == 0 {
   131  					continue // empty interface
   132  				}
   133  
   134  				// T interface, U interface
   135  				if !types.Identical(T, U) {
   136  					if types.AssignableTo(U, T) {
   137  						to = append(to, U)
   138  					}
   139  					if types.AssignableTo(T, U) {
   140  						from = append(from, U)
   141  					}
   142  				}
   143  			} else {
   144  				// T interface, U concrete
   145  				if types.AssignableTo(U, T) {
   146  					to = append(to, U)
   147  				} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
   148  					to = append(to, pU)
   149  				}
   150  			}
   151  		} else if isInterface(U) {
   152  			if msets.MethodSet(U).Len() == 0 {
   153  				continue // empty interface
   154  			}
   155  
   156  			// T concrete, U interface
   157  			if types.AssignableTo(T, U) {
   158  				from = append(from, U)
   159  			} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
   160  				fromPtr = append(fromPtr, U)
   161  			}
   162  		}
   163  	}
   164  
   165  	var pos interface{} = qpos
   166  	if nt, ok := deref(T).(*types.Named); ok {
   167  		pos = nt.Obj()
   168  	}
   169  
   170  	// Sort types (arbitrarily) to ensure test determinism.
   171  	sort.Sort(typesByString(to))
   172  	sort.Sort(typesByString(from))
   173  	sort.Sort(typesByString(fromPtr))
   174  
   175  	var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
   176  	if method != nil {
   177  		for _, t := range to {
   178  			toMethod = append(toMethod,
   179  				types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
   180  		}
   181  		for _, t := range from {
   182  			fromMethod = append(fromMethod,
   183  				types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
   184  		}
   185  		for _, t := range fromPtr {
   186  			fromPtrMethod = append(fromPtrMethod,
   187  				types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
   188  		}
   189  	}
   190  
   191  	q.Output(lprog.Fset, &implementsResult{
   192  		qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
   193  	})
   194  	return nil
   195  }
   196  
   197  type implementsResult struct {
   198  	qpos *queryPos
   199  
   200  	t       types.Type   // queried type (not necessarily named)
   201  	pos     interface{}  // pos of t (*types.Name or *QueryPos)
   202  	to      []types.Type // named or ptr-to-named types assignable to interface T
   203  	from    []types.Type // named interfaces assignable from T
   204  	fromPtr []types.Type // named interfaces assignable only from *T
   205  
   206  	// if a method was queried:
   207  	method        *types.Func        // queried method
   208  	toMethod      []*types.Selection // method of type to[i], if any
   209  	fromMethod    []*types.Selection // method of type from[i], if any
   210  	fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
   211  }
   212  
   213  func (r *implementsResult) PrintPlain(printf printfFunc) {
   214  	relation := "is implemented by"
   215  
   216  	meth := func(sel *types.Selection) {
   217  		if sel != nil {
   218  			printf(sel.Obj(), "\t%s method (%s).%s",
   219  				relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
   220  		}
   221  	}
   222  
   223  	if isInterface(r.t) {
   224  		if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
   225  			printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
   226  			return
   227  		}
   228  
   229  		if r.method == nil {
   230  			printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
   231  		} else {
   232  			printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
   233  		}
   234  
   235  		// Show concrete types (or methods) first; use two passes.
   236  		for i, sub := range r.to {
   237  			if !isInterface(sub) {
   238  				if r.method == nil {
   239  					printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
   240  						relation, typeKind(sub), r.qpos.typeString(sub))
   241  				} else {
   242  					meth(r.toMethod[i])
   243  				}
   244  			}
   245  		}
   246  		for i, sub := range r.to {
   247  			if isInterface(sub) {
   248  				if r.method == nil {
   249  					printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
   250  						relation, typeKind(sub), r.qpos.typeString(sub))
   251  				} else {
   252  					meth(r.toMethod[i])
   253  				}
   254  			}
   255  		}
   256  
   257  		relation = "implements"
   258  		for i, super := range r.from {
   259  			if r.method == nil {
   260  				printf(super.(*types.Named).Obj(), "\t%s %s",
   261  					relation, r.qpos.typeString(super))
   262  			} else {
   263  				meth(r.fromMethod[i])
   264  			}
   265  		}
   266  	} else {
   267  		relation = "implements"
   268  
   269  		if r.from != nil {
   270  			if r.method == nil {
   271  				printf(r.pos, "%s type %s",
   272  					typeKind(r.t), r.qpos.typeString(r.t))
   273  			} else {
   274  				printf(r.method, "concrete method %s",
   275  					r.qpos.objectString(r.method))
   276  			}
   277  			for i, super := range r.from {
   278  				if r.method == nil {
   279  					printf(super.(*types.Named).Obj(), "\t%s %s",
   280  						relation, r.qpos.typeString(super))
   281  				} else {
   282  					meth(r.fromMethod[i])
   283  				}
   284  			}
   285  		}
   286  		if r.fromPtr != nil {
   287  			if r.method == nil {
   288  				printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
   289  			} else {
   290  				// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
   291  				printf(r.method, "concrete method %s",
   292  					r.qpos.objectString(r.method))
   293  			}
   294  
   295  			for i, psuper := range r.fromPtr {
   296  				if r.method == nil {
   297  					printf(psuper.(*types.Named).Obj(), "\t%s %s",
   298  						relation, r.qpos.typeString(psuper))
   299  				} else {
   300  					meth(r.fromPtrMethod[i])
   301  				}
   302  			}
   303  		} else if r.from == nil {
   304  			printf(r.pos, "%s type %s implements only interface{}",
   305  				typeKind(r.t), r.qpos.typeString(r.t))
   306  		}
   307  	}
   308  }
   309  
   310  func (r *implementsResult) JSON(fset *token.FileSet) []byte {
   311  	var method *serial.DescribeMethod
   312  	if r.method != nil {
   313  		method = &serial.DescribeMethod{
   314  			Name: r.qpos.objectString(r.method),
   315  			Pos:  fset.Position(r.method.Pos()).String(),
   316  		}
   317  	}
   318  	return toJSON(&serial.Implements{
   319  		T:                       makeImplementsType(r.t, fset),
   320  		AssignableTo:            makeImplementsTypes(r.to, fset),
   321  		AssignableFrom:          makeImplementsTypes(r.from, fset),
   322  		AssignableFromPtr:       makeImplementsTypes(r.fromPtr, fset),
   323  		AssignableToMethod:      methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
   324  		AssignableFromMethod:    methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
   325  		AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
   326  		Method:                  method,
   327  	})
   328  
   329  }
   330  
   331  func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
   332  	var r []serial.ImplementsType
   333  	for _, t := range tt {
   334  		r = append(r, makeImplementsType(t, fset))
   335  	}
   336  	return r
   337  }
   338  
   339  func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
   340  	var pos token.Pos
   341  	if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
   342  		pos = nt.Obj().Pos()
   343  	}
   344  	return serial.ImplementsType{
   345  		Name: T.String(),
   346  		Pos:  fset.Position(pos).String(),
   347  		Kind: typeKind(T),
   348  	}
   349  }
   350  
   351  // typeKind returns a string describing the underlying kind of type,
   352  // e.g. "slice", "array", "struct".
   353  func typeKind(T types.Type) string {
   354  	s := reflect.TypeOf(T.Underlying()).String()
   355  	return strings.ToLower(strings.TrimPrefix(s, "*types."))
   356  }
   357  
   358  func isInterface(T types.Type) bool { return types.IsInterface(T) }
   359  
   360  type typesByString []types.Type
   361  
   362  func (p typesByString) Len() int           { return len(p) }
   363  func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
   364  func (p typesByString) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }