go.tools/oracle: implements: now shows whole-program implements relation for selected type.

(Previously it showed the implements relation for all types within the query package.)

R=crawshaw
CC=golang-dev
https://golang.org/cl/42000043
This commit is contained in:
Alan Donovan 2013-12-13 18:00:55 -05:00
parent f119874203
commit 8b9d1fd507
14 changed files with 492 additions and 150 deletions

View File

@ -36,8 +36,8 @@ var ptalogFlag = flag.String("ptalog", "",
var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.") var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.")
// TODO(adonovan): eliminate or flip this flag after PTA presolver is implemented. // TODO(adonovan): flip this flag after PTA presolver is implemented.
var reflectFlag = flag.Bool("reflect", true, "Analyze reflection soundly (slow).") var reflectFlag = flag.Bool("reflect", false, "Analyze reflection soundly (slow).")
const useHelp = "Run 'oracle -help' for more information.\n" const useHelp = "Run 'oracle -help' for more information.\n"

View File

@ -96,4 +96,9 @@ Emacs: use JSON to get the raw information from the oracle. Don't
open an editor buffer for simpler queries, just jump to the result open an editor buffer for simpler queries, just jump to the result
and/or display it in the modeline. and/or display it in the modeline.
Emacs: go-root-and-paths depends on the current buffer, so be sure to
call it from within the source file, not the *go-oracle* buffer:
the user may have switched workspaces and the oracle should run in
the new one.
Support other editors: vim, Eclipse, Sublime, etc. Support other editors: vim, Eclipse, Sublime, etc.

View File

@ -5,99 +5,197 @@
package oracle package oracle
import ( import (
"fmt"
"go/ast"
"go/token" "go/token"
"reflect"
"sort"
"strings"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/serial" "code.google.com/p/go.tools/oracle/serial"
) )
// Implements displays the "implements" relation among all // Implements displays the "implements" relation as it pertains to the
// package-level named types in the package containing the query // selected type.
// position.
//
// TODO(adonovan): more features:
// - should we include pairs of types belonging to
// different packages in the 'implements' relation?
// - should we restrict the query to the type declaration identified
// by the query position, if any, and use all types in the package
// otherwise?
// - should we show types that are local to functions?
// They can only have methods via promotion.
// - abbreviate the set of concrete types implementing the empty
// interface.
// - should we scan the instruction stream for MakeInterface
// instructions and report which concrete->interface conversions
// actually occur, with examples? (NB: this is not a conservative
// answer due to ChangeInterface, i.e. subtyping among interfaces.)
// //
func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
pkg := qpos.info.Pkg // 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")
}
// Compute set of named interface/concrete types at package level. // Find all named types, even local types (which can have
var interfaces, concretes []*types.Named // methods via promotion) and the built-in "error".
scope := pkg.Scope() //
for _, name := range scope.Names() { // TODO(adonovan): include all packages in PTA scope too?
mem := scope.Lookup(name) // i.e. don't reduceScope?
if t, ok := mem.(*types.TypeName); ok { //
nt := t.Type().(*types.Named) var allNamed []types.Type
if _, ok := nt.Underlying().(*types.Interface); ok { for _, info := range o.typeInfo {
interfaces = append(interfaces, nt) for id, obj := range info.Objects {
if obj, ok := obj.(*types.TypeName); ok && obj.Pos() == id.Pos() {
allNamed = append(allNamed, obj.Type())
}
}
}
allNamed = append(allNamed, types.Universe.Lookup("error").Type())
// Test each named type.
var to, from, fromPtr []types.Type
for _, U := range allNamed {
if isInterface(T) {
if T.MethodSet().Len() == 0 {
continue // empty interface
}
if isInterface(U) {
if U.MethodSet().Len() == 0 {
continue // empty interface
}
// T interface, U interface
if !types.IsIdentical(T, U) {
if types.IsAssignableTo(U, T) {
to = append(to, U)
}
if types.IsAssignableTo(T, U) {
from = append(from, U)
}
}
} else { } else {
concretes = append(concretes, nt) // T interface, U concrete
if types.IsAssignableTo(U, T) {
to = append(to, U)
} else if pU := types.NewPointer(U); types.IsAssignableTo(pU, T) {
to = append(to, pU)
}
}
} else if isInterface(U) {
if U.MethodSet().Len() == 0 {
continue // empty interface
}
// T concrete, U interface
if types.IsAssignableTo(T, U) {
from = append(from, U)
} else if pT := types.NewPointer(T); types.IsAssignableTo(pT, U) {
fromPtr = append(fromPtr, U)
} }
} }
} }
// For each interface, show the concrete types that implement it. var pos interface{} = qpos
var facts []implementsFact if nt, ok := deref(T).(*types.Named); ok {
for _, iface := range interfaces { pos = nt.Obj()
fact := implementsFact{iface: iface}
for _, conc := range concretes {
if types.IsAssignableTo(conc, iface) {
fact.conc = conc
} else if ptr := types.NewPointer(conc); types.IsAssignableTo(ptr, iface) {
fact.conc = ptr
} else {
continue
}
facts = append(facts, fact)
}
} }
// TODO(adonovan): sort facts to ensure test nondeterminism.
return &implementsResult{o.fset, facts}, nil // Sort types (arbitrarily) to ensure test nondeterminism.
} sort.Sort(typesByString(to))
sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr))
type implementsFact struct { return &implementsResult{T, pos, to, from, fromPtr}, nil
iface *types.Named
conc types.Type // Named or Pointer(Named)
} }
type implementsResult struct { type implementsResult struct {
fset *token.FileSet t types.Type // queried type (not necessarily named)
facts []implementsFact // facts are grouped by interface 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) { func (r *implementsResult) display(printf printfFunc) {
var prevIface *types.Named if isInterface(r.t) {
for _, fact := range r.facts { if r.t.MethodSet().Len() == 0 {
if fact.iface != prevIface { printf(r.pos, "empty interface type %s", r.t)
printf(fact.iface.Obj(), "\tInterface %s:", fact.iface) return
prevIface = fact.iface }
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)
} }
printf(deref(fact.conc).(*types.Named).Obj(), "\t\t%s", fact.conc)
} }
} }
func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
var facts []*serial.Implements res.Implements = &serial.Implements{
for _, fact := range r.facts { T: makeImplementsType(r.t, fset),
facts = append(facts, &serial.Implements{ AssignableTo: makeImplementsTypes(r.to, fset),
I: fact.iface.String(), AssignableFrom: makeImplementsTypes(r.from, fset),
IPos: fset.Position(fact.iface.Obj().Pos()).String(), AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
C: fact.conc.String(),
CPos: fset.Position(deref(fact.conc).(*types.Named).Obj().Pos()).String(),
})
} }
res.Implements = facts
} }
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] }

View File

@ -94,7 +94,7 @@ type modeInfo struct {
} }
var modes = []*modeInfo{ var modes = []*modeInfo{
// Pointer analyses: (whole program) // Pointer analyses, whole program:
{"callees", needPTA | needExactPos, callees}, {"callees", needPTA | needExactPos, callees},
{"callers", needPTA | needPos, callers}, {"callers", needPTA | needPos, callers},
{"callgraph", needPTA, callgraph}, {"callgraph", needPTA, callgraph},
@ -102,12 +102,14 @@ var modes = []*modeInfo{
{"peers", needPTA | needSSADebug | needPos, peers}, {"peers", needPTA | needSSADebug | needPos, peers},
{"pointsto", needPTA | needSSADebug | needExactPos, pointsto}, {"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
// Type-based analyses: (modular, mostly) // Type-based, modular analyses:
{"definition", needPos, definition}, {"definition", needPos, definition},
{"describe", needExactPos, describe}, {"describe", needExactPos, describe},
{"freevars", needPos, freevars}, {"freevars", needPos, freevars},
{"implements", needPos, implements},
{"referrers", needRetainTypeInfo | needPos, referrers}, // (whole-program) // Type-based, whole-program analyses:
{"implements", needRetainTypeInfo | needPos, implements},
{"referrers", needRetainTypeInfo | needPos, referrers},
} }
func findMode(mode string) *modeInfo { func findMode(mode string) *modeInfo {

View File

@ -210,10 +210,12 @@ func TestOracle(t *testing.T) {
"testdata/src/main/reflection.go", "testdata/src/main/reflection.go",
"testdata/src/main/what.go", "testdata/src/main/what.go",
// JSON: // JSON:
// TODO(adonovan): most of these are very similar; combine them.
"testdata/src/main/callgraph-json.go", "testdata/src/main/callgraph-json.go",
"testdata/src/main/calls-json.go", "testdata/src/main/calls-json.go",
"testdata/src/main/peers-json.go", "testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go", "testdata/src/main/describe-json.go",
"testdata/src/main/implements-json.go",
"testdata/src/main/pointsto-json.go", "testdata/src/main/pointsto-json.go",
"testdata/src/main/referrers-json.go", "testdata/src/main/referrers-json.go",
"testdata/src/main/what-json.go", "testdata/src/main/what-json.go",

View File

@ -99,15 +99,24 @@ type FreeVar struct {
Type string `json:"type"` // type of the expression Type string `json:"type"` // type of the expression
} }
// An Implements is one element of the result of an 'implements' query. // An Implements contains the result of an 'implements' query.
// Each one indicates a row in the "implements" relation over
// package-level named types defined by the package containing the // It describes the queried type, the set of named non-empty interface
// selection. // types to which it is assignable, and the set of named/*named types
// (concrete or non-empty interface) which may be assigned to it.
//
type Implements struct { type Implements struct {
I string `json:"i"` // full name of the interface type T ImplementsType `json:"type,omitempty"` // the queried type
IPos string `json:"ipos"` // location of its definition AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
C string `json:"c"` // full name of the concrete type AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
CPos string `json:"cpos"` // location of its definition AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
}
// An ImplementsType describes a single type as part of an 'implements' query.
type ImplementsType struct {
Name string `json:"name"` // full name of the type
Pos string `json:"pos"` // location of its definition
Kind string `json:"kind"` // "basic", "array", etc
} }
// A SyntaxNode is one element of a stack of enclosing syntax nodes in // A SyntaxNode is one element of a stack of enclosing syntax nodes in
@ -222,23 +231,25 @@ type PTAWarning struct {
// A Result is the common result of any oracle query. // A Result is the common result of any oracle query.
// It contains a query-specific result element. // It contains a query-specific result element.
// //
// TODO(adonovan): perhaps include other info such as: analysis scope,
// raw query position, stack of ast nodes, query package, etc.
type Result struct { type Result struct {
Mode string `json:"mode"` // mode of the query Mode string `json:"mode"` // mode of the query
// Exactly one of the following fields is populated: // Exactly one of the following fields is populated:
// the one specified by 'mode'. // the one specified by 'mode'.
Callees *Callees `json:"callees,omitempty"` Callees *Callees `json:"callees,omitempty"`
Callers []Caller `json:"callers,omitempty"` Callers []Caller `json:"callers,omitempty"`
Callgraph []CallGraph `json:"callgraph,omitempty"` Callgraph []CallGraph `json:"callgraph,omitempty"`
Callstack *CallStack `json:"callstack,omitempty"` Callstack *CallStack `json:"callstack,omitempty"`
Definition *Definition `json:"definition,omitempty"` Definition *Definition `json:"definition,omitempty"`
Describe *Describe `json:"describe,omitempty"` Describe *Describe `json:"describe,omitempty"`
Freevars []*FreeVar `json:"freevars,omitempty"` Freevars []*FreeVar `json:"freevars,omitempty"`
Implements []*Implements `json:"implements,omitempty"` Implements *Implements `json:"implements,omitempty"`
Peers *Peers `json:"peers,omitempty"` Peers *Peers `json:"peers,omitempty"`
PointsTo []PointsTo `json:"pointsto,omitempty"` PointsTo []PointsTo `json:"pointsto,omitempty"`
Referrers *Referrers `json:"referrers,omitempty"` Referrers *Referrers `json:"referrers,omitempty"`
What *What `json:"what,omitempty"` What *What `json:"what,omitempty"`
Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis
} }

View File

@ -1,8 +1,6 @@
package describe // @describe pkgdecl "describe" package describe // @describe pkgdecl "describe"
// @implements implements "^" // Tests of 'describe' query, -format=json.
// Tests of 'describe' and 'implements' queries, -format=json.
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.
// See describe-json.golden for expected query results. // See describe-json.golden for expected query results.

View File

@ -11,75 +11,58 @@
{ {
"name": "C", "name": "C",
"type": "int", "type": "int",
"pos": "testdata/src/main/describe-json.go:27:6", "pos": "testdata/src/main/describe-json.go:25:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (C) f()", "name": "method (C) f()",
"pos": "testdata/src/main/describe-json.go:30:12" "pos": "testdata/src/main/describe-json.go:28:12"
} }
] ]
}, },
{ {
"name": "D", "name": "D",
"type": "struct{}", "type": "struct{}",
"pos": "testdata/src/main/describe-json.go:28:6", "pos": "testdata/src/main/describe-json.go:26:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (*D) f()", "name": "method (*D) f()",
"pos": "testdata/src/main/describe-json.go:31:13" "pos": "testdata/src/main/describe-json.go:29:13"
} }
] ]
}, },
{ {
"name": "I", "name": "I",
"type": "interface{f()}", "type": "interface{f()}",
"pos": "testdata/src/main/describe-json.go:23:6", "pos": "testdata/src/main/describe-json.go:21:6",
"kind": "type", "kind": "type",
"methods": [ "methods": [
{ {
"name": "method (I) f()", "name": "method (I) f()",
"pos": "testdata/src/main/describe-json.go:24:2" "pos": "testdata/src/main/describe-json.go:22:2"
} }
] ]
}, },
{ {
"name": "main", "name": "main",
"type": "func()", "type": "func()",
"pos": "testdata/src/main/describe-json.go:9:6", "pos": "testdata/src/main/describe-json.go:7:6",
"kind": "func" "kind": "func"
} }
] ]
} }
} }
}-------- @implements implements --------
{
"mode": "implements",
"implements": [
{
"i": "describe.I",
"ipos": "testdata/src/main/describe-json.go:23:6",
"c": "describe.C",
"cpos": "testdata/src/main/describe-json.go:27:6"
},
{
"i": "describe.I",
"ipos": "testdata/src/main/describe-json.go:23:6",
"c": "*describe.D",
"cpos": "testdata/src/main/describe-json.go:28:6"
}
]
}-------- @describe desc-val-p -------- }-------- @describe desc-val-p --------
{ {
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "identifier", "desc": "identifier",
"pos": "testdata/src/main/describe-json.go:11:2", "pos": "testdata/src/main/describe-json.go:9:2",
"detail": "value", "detail": "value",
"value": { "value": {
"type": "*int", "type": "*int",
"objpos": "testdata/src/main/describe-json.go:11:2" "objpos": "testdata/src/main/describe-json.go:9:2"
} }
} }
}-------- @describe desc-val-i -------- }-------- @describe desc-val-i --------
@ -87,11 +70,11 @@
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "identifier", "desc": "identifier",
"pos": "testdata/src/main/describe-json.go:18:8", "pos": "testdata/src/main/describe-json.go:16:8",
"detail": "value", "detail": "value",
"value": { "value": {
"type": "I", "type": "I",
"objpos": "testdata/src/main/describe-json.go:14:6" "objpos": "testdata/src/main/describe-json.go:12:6"
} }
} }
}-------- @describe desc-stmt -------- }-------- @describe desc-stmt --------
@ -99,7 +82,7 @@
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "go statement", "desc": "go statement",
"pos": "testdata/src/main/describe-json.go:20:2", "pos": "testdata/src/main/describe-json.go:18:2",
"detail": "unknown" "detail": "unknown"
} }
}-------- @describe desc-type-C -------- }-------- @describe desc-type-C --------
@ -107,16 +90,16 @@
"mode": "describe", "mode": "describe",
"describe": { "describe": {
"desc": "definition of type C (size 8, align 8)", "desc": "definition of type C (size 8, align 8)",
"pos": "testdata/src/main/describe-json.go:27:6", "pos": "testdata/src/main/describe-json.go:25:6",
"detail": "type", "detail": "type",
"type": { "type": {
"type": "C", "type": "C",
"namepos": "testdata/src/main/describe-json.go:27:6", "namepos": "testdata/src/main/describe-json.go:25:6",
"namedef": "int", "namedef": "int",
"methods": [ "methods": [
{ {
"name": "method (C) f()", "name": "method (C) f()",
"pos": "testdata/src/main/describe-json.go:30:12" "pos": "testdata/src/main/describe-json.go:28:12"
} }
] ]
} }

View File

@ -0,0 +1,27 @@
package main
// Tests of 'implements' query, -output=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See implements.golden for expected query results.
func main() {
}
type E interface{} // @implements E "E"
type F interface { // @implements F "F"
f()
}
type FG interface { // @implements FG "FG"
f()
g() []int // @implements slice "..int"
}
type C int // @implements C "C"
type D struct{}
func (c *C) f() {} // @implements starC ".C"
func (d D) f() {} // @implements D "D"
func (d *D) g() []int { return nil } // @implements starD ".D"

View File

@ -0,0 +1,152 @@
-------- @implements E --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.E",
"pos": "testdata/src/main/implements-json.go:10:6",
"kind": "interface"
}
}
}-------- @implements F --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
},
"to": [
{
"name": "*main.C",
"pos": "testdata/src/main/implements-json.go:21:6",
"kind": "pointer"
},
{
"name": "main.D",
"pos": "testdata/src/main/implements-json.go:22:6",
"kind": "struct"
},
{
"name": "main.FG",
"pos": "testdata/src/main/implements-json.go:16:6",
"kind": "interface"
}
]
}
}-------- @implements FG --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.FG",
"pos": "testdata/src/main/implements-json.go:16:6",
"kind": "interface"
},
"to": [
{
"name": "*main.D",
"pos": "testdata/src/main/implements-json.go:22:6",
"kind": "pointer"
}
],
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
}
]
}
}-------- @implements slice --------
{
"mode": "implements",
"implements": {
"type": {
"name": "[]int",
"pos": "-",
"kind": "slice"
}
}
}-------- @implements C --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.C",
"pos": "testdata/src/main/implements-json.go:21:6",
"kind": "basic"
},
"fromptr": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
}
]
}
}-------- @implements starC --------
{
"mode": "implements",
"implements": {
"type": {
"name": "*main.C",
"pos": "testdata/src/main/implements-json.go:21:6",
"kind": "pointer"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
}
]
}
}-------- @implements D --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.D",
"pos": "testdata/src/main/implements-json.go:22:6",
"kind": "struct"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
}
],
"fromptr": [
{
"name": "main.FG",
"pos": "testdata/src/main/implements-json.go:16:6",
"kind": "interface"
}
]
}
}-------- @implements starD --------
{
"mode": "implements",
"implements": {
"type": {
"name": "*main.D",
"pos": "testdata/src/main/implements-json.go:22:6",
"kind": "pointer"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-json.go:12:6",
"kind": "interface"
},
{
"name": "main.FG",
"pos": "testdata/src/main/implements-json.go:16:6",
"kind": "interface"
}
]
}
}

View File

@ -4,26 +4,37 @@ package main
// See go.tools/oracle/oracle_test.go for explanation. // See go.tools/oracle/oracle_test.go for explanation.
// See implements.golden for expected query results. // See implements.golden for expected query results.
// @implements impl "" import _ "lib"
import _ "sort"
func main() { func main() {
} }
type E interface{} type E interface{} // @implements E "E"
type F interface { type F interface { // @implements F "F"
f() f()
} }
type FG interface { type FG interface { // @implements FG "FG"
f() f()
g() int g() []int // @implements slice "..int"
} }
type C int type C int // @implements C "C"
type D struct{} type D struct{}
func (c *C) f() {} func (c *C) f() {} // @implements starC ".C"
func (d D) f() {} func (d D) f() {} // @implements D "D"
func (d *D) g() int { return 0 } func (d *D) g() []int { return nil } // @implements starD ".D"
type sorter []int // @implements sorter "sorter"
func (sorter) Len() int { return 0 }
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface { // @implements I "I"
Method(*int) *int
}

View File

@ -1,10 +1,44 @@
-------- @implements impl -------- -------- @implements E --------
Interface main.E: empty interface type main.E
main.C
main.D -------- @implements F --------
Interface main.F: interface type main.F
*main.C is implemented by pointer type *main.C
main.D is implemented by struct type main.D
Interface main.FG: is implemented by interface type main.FG
*main.D
-------- @implements FG --------
interface type main.FG
is implemented by pointer type *main.D
implements main.F
-------- @implements slice --------
slice type []int implements only interface{}
-------- @implements C --------
pointer type *main.C
implements main.F
-------- @implements starC --------
pointer type *main.C
implements main.F
-------- @implements D --------
struct type main.D
implements main.F
pointer type *main.D
implements main.FG
-------- @implements starD --------
pointer type *main.D
implements main.F
implements main.FG
-------- @implements sorter --------
slice type main.sorter
implements sort.Interface
-------- @implements I --------
interface type main.I
is implemented by basic type lib.Type

View File

@ -22,7 +22,7 @@ variable declaration statement
block block
function declaration function declaration
source file source file
modes: [callers callgraph callstack describe freevars implements pointsto] modes: [callers callgraph callstack describe freevars pointsto]
srcdir: testdata/src srcdir: testdata/src
import path: main import path: main

View File

@ -34,17 +34,23 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext) srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext)
// Determine which query modes are applicable to the selection. // Determine which query modes are applicable to the selection.
// TODO(adonovan): refactor: make each minfo have an 'enable'
// predicate over qpos.
enable := map[string]bool{ enable := map[string]bool{
"callgraph": true, // whole program; always enabled "callgraph": true, // whole program; always enabled
"implements": true, // whole package; always enabled "describe": true, // any syntax; always enabled
"freevars": qpos.end > qpos.start, // nonempty selection?
"describe": true, // any syntax; always enabled
} }
if qpos.end > qpos.start {
enable["freevars"] = true // nonempty selection?
}
for _, n := range qpos.path { for _, n := range qpos.path {
switch n := n.(type) { switch n := n.(type) {
case *ast.Ident: case *ast.Ident:
enable["definition"] = true enable["definition"] = true
enable["referrers"] = true enable["referrers"] = true
enable["implements"] = true
case *ast.CallExpr: case *ast.CallExpr:
enable["callees"] = true enable["callees"] = true
case *ast.FuncDecl: case *ast.FuncDecl:
@ -58,6 +64,19 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) {
} }
} }
// For implements, we approximate findInterestingNode.
if _, ok := enable["implements"]; !ok {
switch n.(type) {
case *ast.ArrayType,
*ast.StructType,
*ast.FuncType,
*ast.InterfaceType,
*ast.MapType,
*ast.ChanType:
enable["implements"] = true
}
}
// For pointsto, we approximate findInterestingNode. // For pointsto, we approximate findInterestingNode.
if _, ok := enable["pointsto"]; !ok { if _, ok := enable["pointsto"]; !ok {
switch n.(type) { switch n.(type) {