331 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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 oracle
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/go/loader"
 | |
| 	"golang.org/x/tools/go/types"
 | |
| 	"golang.org/x/tools/go/types/typeutil"
 | |
| 	"golang.org/x/tools/oracle/serial"
 | |
| )
 | |
| 
 | |
| // Implements displays the "implements" relation as it pertains to the
 | |
| // selected type within a single package.
 | |
| // 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)
 | |
| 
 | |
| 	if err := importQueryPackage(q.Pos, &lconf); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Load/parse/type-check the program.
 | |
| 	lprog, err := lconf.Load()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	q.Fset = lprog.Fset
 | |
| 
 | |
| 	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()
 | |
| 			}
 | |
| 		}
 | |
| 	case actionType:
 | |
| 		T = qpos.info.TypeOf(path[0].(ast.Expr))
 | |
| 	}
 | |
| 	if T == nil {
 | |
| 		return fmt.Errorf("no type or method here")
 | |
| 	}
 | |
| 
 | |
| 	// Find all named types, even local types (which can have
 | |
| 	// methods via promotion) and the built-in "error".
 | |
| 	//
 | |
| 	// TODO(adonovan): include all packages in PTA scope too?
 | |
| 	// i.e. don't reduceScope?
 | |
| 	//
 | |
| 	var allNamed []types.Type
 | |
| 	for _, info := range lprog.AllPackages {
 | |
| 		for _, obj := range info.Defs {
 | |
| 			if obj, ok := obj.(*types.TypeName); ok {
 | |
| 				allNamed = append(allNamed, obj.Type())
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	allNamed = append(allNamed, types.Universe.Lookup("error").Type())
 | |
| 
 | |
| 	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.result = &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) display(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) toSerial(res *serial.Result, fset *token.FileSet) {
 | |
| 	res.Implements = &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),
 | |
| 	}
 | |
| 	if r.method != nil {
 | |
| 		res.Implements.Method = &serial.DescribeMethod{
 | |
| 			Name: r.qpos.objectString(r.method),
 | |
| 			Pos:  fset.Position(r.method.Pos()).String(),
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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] }
 |