| // 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/types" |
| "golang.org/x/tools/oracle/serial" |
| ) |
| |
| // Implements displays the "implements" relation as it pertains to the |
| // selected type. |
| // |
| func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { |
| // Find the selected type. |
| // TODO(adonovan): fix: make it work on qualified Idents too. |
| path, action := findInterestingNode(qpos.info, qpos.path) |
| if action != actionType { |
| return nil, fmt.Errorf("no type here") |
| } |
| T := qpos.info.TypeOf(path[0].(ast.Expr)) |
| if T == nil { |
| return nil, fmt.Errorf("no type 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 o.typeInfo { |
| 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 types.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)) |
| |
| return &implementsResult{T, pos, to, from, fromPtr}, nil |
| } |
| |
| type implementsResult struct { |
| 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 |
| } |
| |
| func (r *implementsResult) display(printf printfFunc) { |
| if isInterface(r.t) { |
| if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset |
| printf(r.pos, "empty interface type %s", r.t) |
| return |
| } |
| |
| printf(r.pos, "interface type %s", r.t) |
| // Show concrete types first; use two passes. |
| for _, sub := range r.to { |
| if !isInterface(sub) { |
| printf(deref(sub).(*types.Named).Obj(), "\tis implemented by %s type %s", |
| typeKind(sub), sub) |
| } |
| } |
| for _, sub := range r.to { |
| if isInterface(sub) { |
| printf(deref(sub).(*types.Named).Obj(), "\tis implemented by %s type %s", typeKind(sub), sub) |
| } |
| } |
| |
| for _, super := range r.from { |
| printf(super.(*types.Named).Obj(), "\timplements %s", super) |
| } |
| } else { |
| if r.from != nil { |
| printf(r.pos, "%s type %s", typeKind(r.t), r.t) |
| for _, super := range r.from { |
| printf(super.(*types.Named).Obj(), "\timplements %s", super) |
| } |
| } |
| if r.fromPtr != nil { |
| printf(r.pos, "pointer type *%s", r.t) |
| for _, psuper := range r.fromPtr { |
| printf(psuper.(*types.Named).Obj(), "\timplements %s", psuper) |
| } |
| } else if r.from == nil { |
| printf(r.pos, "%s type %s implements only interface{}", typeKind(r.t), 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), |
| } |
| } |
| |
| 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 { |
| _, isI := T.Underlying().(*types.Interface) |
| return isI |
| } |
| |
| 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] } |