diff --git a/cmd/oracle/main.go b/cmd/oracle/main.go index cd4bf155..f7bbce3c 100644 --- a/cmd/oracle/main.go +++ b/cmd/oracle/main.go @@ -36,8 +36,8 @@ var ptalogFlag = flag.String("ptalog", "", 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. -var reflectFlag = flag.Bool("reflect", true, "Analyze reflection soundly (slow).") +// TODO(adonovan): flip this flag after PTA presolver is implemented. +var reflectFlag = flag.Bool("reflect", false, "Analyze reflection soundly (slow).") const useHelp = "Run 'oracle -help' for more information.\n" diff --git a/oracle/TODO b/oracle/TODO index 0951de1e..014f8044 100644 --- a/oracle/TODO +++ b/oracle/TODO @@ -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 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. diff --git a/oracle/implements.go b/oracle/implements.go index ec5924f4..b5a577a9 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -5,99 +5,197 @@ 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 among all -// package-level named types in the package containing the query -// 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.) +// Implements displays the "implements" relation as it pertains to the +// selected type. // 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. - var interfaces, concretes []*types.Named - scope := pkg.Scope() - for _, name := range scope.Names() { - mem := scope.Lookup(name) - if t, ok := mem.(*types.TypeName); ok { - nt := t.Type().(*types.Named) - if _, ok := nt.Underlying().(*types.Interface); ok { - interfaces = append(interfaces, nt) + // 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 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 { - 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 facts []implementsFact - for _, iface := range interfaces { - 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) - } + var pos interface{} = qpos + if nt, ok := deref(T).(*types.Named); ok { + pos = nt.Obj() } - // 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 { - iface *types.Named - conc types.Type // Named or Pointer(Named) + return &implementsResult{T, pos, to, from, fromPtr}, nil } type implementsResult struct { - fset *token.FileSet - facts []implementsFact // facts are grouped by interface + 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) { - var prevIface *types.Named - for _, fact := range r.facts { - if fact.iface != prevIface { - printf(fact.iface.Obj(), "\tInterface %s:", fact.iface) - prevIface = fact.iface + if isInterface(r.t) { + if r.t.MethodSet().Len() == 0 { + 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) } - printf(deref(fact.conc).(*types.Named).Obj(), "\t\t%s", fact.conc) } } func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { - var facts []*serial.Implements - for _, fact := range r.facts { - facts = append(facts, &serial.Implements{ - I: fact.iface.String(), - IPos: fset.Position(fact.iface.Obj().Pos()).String(), - C: fact.conc.String(), - CPos: fset.Position(deref(fact.conc).(*types.Named).Obj().Pos()).String(), - }) + res.Implements = &serial.Implements{ + T: makeImplementsType(r.t, fset), + AssignableTo: makeImplementsTypes(r.to, fset), + AssignableFrom: makeImplementsTypes(r.from, fset), + AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset), } - 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] } diff --git a/oracle/oracle.go b/oracle/oracle.go index 4d4887fd..4be88cec 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -94,7 +94,7 @@ type modeInfo struct { } var modes = []*modeInfo{ - // Pointer analyses: (whole program) + // Pointer analyses, whole program: {"callees", needPTA | needExactPos, callees}, {"callers", needPTA | needPos, callers}, {"callgraph", needPTA, callgraph}, @@ -102,12 +102,14 @@ var modes = []*modeInfo{ {"peers", needPTA | needSSADebug | needPos, peers}, {"pointsto", needPTA | needSSADebug | needExactPos, pointsto}, - // Type-based analyses: (modular, mostly) + // Type-based, modular analyses: {"definition", needPos, definition}, {"describe", needExactPos, describe}, {"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 { diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index 03c3ea2a..db7cadcb 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -210,10 +210,12 @@ func TestOracle(t *testing.T) { "testdata/src/main/reflection.go", "testdata/src/main/what.go", // JSON: + // TODO(adonovan): most of these are very similar; combine them. "testdata/src/main/callgraph-json.go", "testdata/src/main/calls-json.go", "testdata/src/main/peers-json.go", "testdata/src/main/describe-json.go", + "testdata/src/main/implements-json.go", "testdata/src/main/pointsto-json.go", "testdata/src/main/referrers-json.go", "testdata/src/main/what-json.go", diff --git a/oracle/serial/serial.go b/oracle/serial/serial.go index afb95265..316ecffc 100644 --- a/oracle/serial/serial.go +++ b/oracle/serial/serial.go @@ -99,15 +99,24 @@ type FreeVar struct { Type string `json:"type"` // type of the expression } -// An Implements is one element of 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 -// selection. +// An Implements contains the result of an 'implements' query. + +// It describes the queried type, the set of named non-empty interface +// 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 { - I string `json:"i"` // full name of the interface type - IPos string `json:"ipos"` // location of its definition - C string `json:"c"` // full name of the concrete type - CPos string `json:"cpos"` // location of its definition + T ImplementsType `json:"type,omitempty"` // the queried type + AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T + AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T + 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 @@ -222,23 +231,25 @@ type PTAWarning struct { // A Result is the common result of any oracle query. // 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 { Mode string `json:"mode"` // mode of the query // Exactly one of the following fields is populated: // the one specified by 'mode'. - Callees *Callees `json:"callees,omitempty"` - Callers []Caller `json:"callers,omitempty"` - Callgraph []CallGraph `json:"callgraph,omitempty"` - Callstack *CallStack `json:"callstack,omitempty"` - Definition *Definition `json:"definition,omitempty"` - Describe *Describe `json:"describe,omitempty"` - Freevars []*FreeVar `json:"freevars,omitempty"` - Implements []*Implements `json:"implements,omitempty"` - Peers *Peers `json:"peers,omitempty"` - PointsTo []PointsTo `json:"pointsto,omitempty"` - Referrers *Referrers `json:"referrers,omitempty"` - What *What `json:"what,omitempty"` + Callees *Callees `json:"callees,omitempty"` + Callers []Caller `json:"callers,omitempty"` + Callgraph []CallGraph `json:"callgraph,omitempty"` + Callstack *CallStack `json:"callstack,omitempty"` + Definition *Definition `json:"definition,omitempty"` + Describe *Describe `json:"describe,omitempty"` + Freevars []*FreeVar `json:"freevars,omitempty"` + Implements *Implements `json:"implements,omitempty"` + Peers *Peers `json:"peers,omitempty"` + PointsTo []PointsTo `json:"pointsto,omitempty"` + Referrers *Referrers `json:"referrers,omitempty"` + What *What `json:"what,omitempty"` Warnings []PTAWarning `json:"warnings,omitempty"` // warnings from pointer analysis } diff --git a/oracle/testdata/src/main/describe-json.go b/oracle/testdata/src/main/describe-json.go index 906bfaf8..1f22d019 100644 --- a/oracle/testdata/src/main/describe-json.go +++ b/oracle/testdata/src/main/describe-json.go @@ -1,8 +1,6 @@ package describe // @describe pkgdecl "describe" -// @implements implements "^" - -// Tests of 'describe' and 'implements' queries, -format=json. +// Tests of 'describe' query, -format=json. // See go.tools/oracle/oracle_test.go for explanation. // See describe-json.golden for expected query results. diff --git a/oracle/testdata/src/main/describe-json.golden b/oracle/testdata/src/main/describe-json.golden index 987b0c32..8baf837e 100644 --- a/oracle/testdata/src/main/describe-json.golden +++ b/oracle/testdata/src/main/describe-json.golden @@ -11,75 +11,58 @@ { "name": "C", "type": "int", - "pos": "testdata/src/main/describe-json.go:27:6", + "pos": "testdata/src/main/describe-json.go:25:6", "kind": "type", "methods": [ { "name": "method (C) f()", - "pos": "testdata/src/main/describe-json.go:30:12" + "pos": "testdata/src/main/describe-json.go:28:12" } ] }, { "name": "D", "type": "struct{}", - "pos": "testdata/src/main/describe-json.go:28:6", + "pos": "testdata/src/main/describe-json.go:26:6", "kind": "type", "methods": [ { "name": "method (*D) f()", - "pos": "testdata/src/main/describe-json.go:31:13" + "pos": "testdata/src/main/describe-json.go:29:13" } ] }, { "name": "I", "type": "interface{f()}", - "pos": "testdata/src/main/describe-json.go:23:6", + "pos": "testdata/src/main/describe-json.go:21:6", "kind": "type", "methods": [ { "name": "method (I) f()", - "pos": "testdata/src/main/describe-json.go:24:2" + "pos": "testdata/src/main/describe-json.go:22:2" } ] }, { "name": "main", "type": "func()", - "pos": "testdata/src/main/describe-json.go:9:6", + "pos": "testdata/src/main/describe-json.go:7:6", "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 -------- { "mode": "describe", "describe": { "desc": "identifier", - "pos": "testdata/src/main/describe-json.go:11:2", + "pos": "testdata/src/main/describe-json.go:9:2", "detail": "value", "value": { "type": "*int", - "objpos": "testdata/src/main/describe-json.go:11:2" + "objpos": "testdata/src/main/describe-json.go:9:2" } } }-------- @describe desc-val-i -------- @@ -87,11 +70,11 @@ "mode": "describe", "describe": { "desc": "identifier", - "pos": "testdata/src/main/describe-json.go:18:8", + "pos": "testdata/src/main/describe-json.go:16:8", "detail": "value", "value": { "type": "I", - "objpos": "testdata/src/main/describe-json.go:14:6" + "objpos": "testdata/src/main/describe-json.go:12:6" } } }-------- @describe desc-stmt -------- @@ -99,7 +82,7 @@ "mode": "describe", "describe": { "desc": "go statement", - "pos": "testdata/src/main/describe-json.go:20:2", + "pos": "testdata/src/main/describe-json.go:18:2", "detail": "unknown" } }-------- @describe desc-type-C -------- @@ -107,16 +90,16 @@ "mode": "describe", "describe": { "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", "type": { "type": "C", - "namepos": "testdata/src/main/describe-json.go:27:6", + "namepos": "testdata/src/main/describe-json.go:25:6", "namedef": "int", "methods": [ { "name": "method (C) f()", - "pos": "testdata/src/main/describe-json.go:30:12" + "pos": "testdata/src/main/describe-json.go:28:12" } ] } diff --git a/oracle/testdata/src/main/implements-json.go b/oracle/testdata/src/main/implements-json.go new file mode 100644 index 00000000..d5f8102b --- /dev/null +++ b/oracle/testdata/src/main/implements-json.go @@ -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" diff --git a/oracle/testdata/src/main/implements-json.golden b/oracle/testdata/src/main/implements-json.golden new file mode 100644 index 00000000..d43969b9 --- /dev/null +++ b/oracle/testdata/src/main/implements-json.golden @@ -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" + } + ] + } +} \ No newline at end of file diff --git a/oracle/testdata/src/main/implements.go b/oracle/testdata/src/main/implements.go index 81154265..0b5ee120 100644 --- a/oracle/testdata/src/main/implements.go +++ b/oracle/testdata/src/main/implements.go @@ -4,26 +4,37 @@ package main // See go.tools/oracle/oracle_test.go for explanation. // See implements.golden for expected query results. -// @implements impl "" +import _ "lib" +import _ "sort" func main() { } -type E interface{} +type E interface{} // @implements E "E" -type F interface { +type F interface { // @implements F "F" f() } -type FG interface { +type FG interface { // @implements FG "FG" f() - g() int + g() []int // @implements slice "..int" } -type C int +type C int // @implements C "C" type D struct{} -func (c *C) f() {} -func (d D) f() {} +func (c *C) f() {} // @implements starC ".C" +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 +} diff --git a/oracle/testdata/src/main/implements.golden b/oracle/testdata/src/main/implements.golden index b4252145..01d41f6a 100644 --- a/oracle/testdata/src/main/implements.golden +++ b/oracle/testdata/src/main/implements.golden @@ -1,10 +1,44 @@ --------- @implements impl -------- - Interface main.E: - main.C - main.D - Interface main.F: - *main.C - main.D - Interface main.FG: - *main.D +-------- @implements E -------- +empty interface type main.E + +-------- @implements F -------- +interface type main.F + is implemented by pointer type *main.C + is implemented by struct type main.D + is implemented by interface type main.FG + +-------- @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 diff --git a/oracle/testdata/src/main/what.golden b/oracle/testdata/src/main/what.golden index 437fdc82..3f832912 100644 --- a/oracle/testdata/src/main/what.golden +++ b/oracle/testdata/src/main/what.golden @@ -22,7 +22,7 @@ variable declaration statement block function declaration source file -modes: [callers callgraph callstack describe freevars implements pointsto] +modes: [callers callgraph callstack describe freevars pointsto] srcdir: testdata/src import path: main diff --git a/oracle/what.go b/oracle/what.go index 5ead2792..108bef20 100644 --- a/oracle/what.go +++ b/oracle/what.go @@ -34,17 +34,23 @@ func what(posFlag string, buildContext *build.Context) (*Result, error) { srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), buildContext) // 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{ - "callgraph": true, // whole program; always enabled - "implements": true, // whole package; always enabled - "freevars": qpos.end > qpos.start, // nonempty selection? - "describe": true, // any syntax; always enabled + "callgraph": true, // whole program; always enabled + "describe": true, // any syntax; always enabled } + + if qpos.end > qpos.start { + enable["freevars"] = true // nonempty selection? + } + for _, n := range qpos.path { switch n := n.(type) { case *ast.Ident: enable["definition"] = true enable["referrers"] = true + enable["implements"] = true case *ast.CallExpr: enable["callees"] = true 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. if _, ok := enable["pointsto"]; !ok { switch n.(type) {