// 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" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.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] }