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