blob: dbdba041268ba3d7cb8db6c6e7c23148930c165d [file] [log] [blame]
Alan Donovan73473792016-02-11 17:57:17 -05001// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8 "fmt"
9 "go/ast"
10 "go/token"
11 "go/types"
12 "reflect"
13 "sort"
14 "strings"
15
Alan Donovan37bb37e2016-02-11 22:57:18 -050016 "golang.org/x/tools/cmd/guru/serial"
Alan Donovan73473792016-02-11 17:57:17 -050017 "golang.org/x/tools/go/loader"
18 "golang.org/x/tools/go/types/typeutil"
Alan Donovan73473792016-02-11 17:57:17 -050019 "golang.org/x/tools/refactor/importgraph"
20)
21
Koichi Shiraishibe3ddff2017-07-10 14:54:58 +090022// The implements function displays the "implements" relation as it pertains to the
Alan Donovan73473792016-02-11 17:57:17 -050023// selected type.
24// If the selection is a method, 'implements' displays
25// the corresponding methods of the types that would have been reported
26// by an implements query on the receiver type.
27//
28func implements(q *Query) error {
29 lconf := loader.Config{Build: q.Build}
30 allowErrors(&lconf)
31
32 qpkg, err := importQueryPackage(q.Pos, &lconf)
33 if err != nil {
34 return err
35 }
36
37 // Set the packages to search.
38 if len(q.Scope) > 0 {
39 // Inspect all packages in the analysis scope, if specified.
40 if err := setPTAScope(&lconf, q.Scope); err != nil {
41 return err
42 }
43 } else {
44 // Otherwise inspect the forward and reverse
45 // transitive closure of the selected package.
46 // (In theory even this is incomplete.)
47 _, rev, _ := importgraph.Build(q.Build)
48 for path := range rev.Search(qpkg) {
49 lconf.ImportWithTests(path)
50 }
51
52 // TODO(adonovan): for completeness, we should also
53 // type-check and inspect function bodies in all
54 // imported packages. This would be expensive, but we
55 // could optimize by skipping functions that do not
56 // contain type declarations. This would require
57 // changing the loader's TypeCheckFuncBodies hook to
58 // provide the []*ast.File.
59 }
60
61 // Load/parse/type-check the program.
62 lprog, err := lconf.Load()
63 if err != nil {
64 return err
65 }
Alan Donovan73473792016-02-11 17:57:17 -050066
67 qpos, err := parseQueryPos(lprog, q.Pos, false)
68 if err != nil {
69 return err
70 }
71
72 // Find the selected type.
73 path, action := findInterestingNode(qpos.info, qpos.path)
74
75 var method *types.Func
76 var T types.Type // selected type (receiver if method != nil)
77
78 switch action {
79 case actionExpr:
80 // method?
81 if id, ok := path[0].(*ast.Ident); ok {
82 if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
83 recv := obj.Type().(*types.Signature).Recv()
84 if recv == nil {
85 return fmt.Errorf("this function is not a method")
86 }
87 method = obj
88 T = recv.Type()
89 }
90 }
Alan Donovan0f5f9fc2016-04-17 12:46:58 -040091
92 // If not a method, use the expression's type.
93 if T == nil {
94 T = qpos.info.TypeOf(path[0].(ast.Expr))
95 }
96
Alan Donovan73473792016-02-11 17:57:17 -050097 case actionType:
98 T = qpos.info.TypeOf(path[0].(ast.Expr))
99 }
100 if T == nil {
Alan Donovan0f5f9fc2016-04-17 12:46:58 -0400101 return fmt.Errorf("not a type, method, or value")
Alan Donovan73473792016-02-11 17:57:17 -0500102 }
103
104 // Find all named types, even local types (which can have
Alan Donovan6e7ee5a2017-02-09 14:54:08 -0500105 // methods due to promotion) and the built-in "error".
106 // We ignore aliases 'type M = N' to avoid duplicate
107 // reporting of the Named type N.
108 var allNamed []*types.Named
Alan Donovan73473792016-02-11 17:57:17 -0500109 for _, info := range lprog.AllPackages {
110 for _, obj := range info.Defs {
Alan Donovan6e7ee5a2017-02-09 14:54:08 -0500111 if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
112 if named, ok := obj.Type().(*types.Named); ok {
113 allNamed = append(allNamed, named)
114 }
Alan Donovan73473792016-02-11 17:57:17 -0500115 }
116 }
117 }
Alan Donovan6e7ee5a2017-02-09 14:54:08 -0500118 allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
Alan Donovan73473792016-02-11 17:57:17 -0500119
120 var msets typeutil.MethodSetCache
121
122 // Test each named type.
123 var to, from, fromPtr []types.Type
124 for _, U := range allNamed {
125 if isInterface(T) {
126 if msets.MethodSet(T).Len() == 0 {
127 continue // empty interface
128 }
129 if isInterface(U) {
130 if msets.MethodSet(U).Len() == 0 {
131 continue // empty interface
132 }
133
134 // T interface, U interface
135 if !types.Identical(T, U) {
136 if types.AssignableTo(U, T) {
137 to = append(to, U)
138 }
139 if types.AssignableTo(T, U) {
140 from = append(from, U)
141 }
142 }
143 } else {
144 // T interface, U concrete
145 if types.AssignableTo(U, T) {
146 to = append(to, U)
147 } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
148 to = append(to, pU)
149 }
150 }
151 } else if isInterface(U) {
152 if msets.MethodSet(U).Len() == 0 {
153 continue // empty interface
154 }
155
156 // T concrete, U interface
157 if types.AssignableTo(T, U) {
158 from = append(from, U)
159 } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
160 fromPtr = append(fromPtr, U)
161 }
162 }
163 }
164
165 var pos interface{} = qpos
166 if nt, ok := deref(T).(*types.Named); ok {
167 pos = nt.Obj()
168 }
169
170 // Sort types (arbitrarily) to ensure test determinism.
171 sort.Sort(typesByString(to))
172 sort.Sort(typesByString(from))
173 sort.Sort(typesByString(fromPtr))
174
175 var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
176 if method != nil {
177 for _, t := range to {
178 toMethod = append(toMethod,
179 types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
180 }
181 for _, t := range from {
182 fromMethod = append(fromMethod,
183 types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
184 }
185 for _, t := range fromPtr {
186 fromPtrMethod = append(fromPtrMethod,
187 types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
188 }
189 }
190
Alan Donovan2da07202016-04-01 15:04:45 -0400191 q.Output(lprog.Fset, &implementsResult{
Alan Donovan73473792016-02-11 17:57:17 -0500192 qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
Alan Donovan2da07202016-04-01 15:04:45 -0400193 })
Alan Donovan73473792016-02-11 17:57:17 -0500194 return nil
195}
196
197type implementsResult struct {
198 qpos *queryPos
199
200 t types.Type // queried type (not necessarily named)
201 pos interface{} // pos of t (*types.Name or *QueryPos)
202 to []types.Type // named or ptr-to-named types assignable to interface T
203 from []types.Type // named interfaces assignable from T
204 fromPtr []types.Type // named interfaces assignable only from *T
205
206 // if a method was queried:
207 method *types.Func // queried method
208 toMethod []*types.Selection // method of type to[i], if any
209 fromMethod []*types.Selection // method of type from[i], if any
210 fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
211}
212
Alan Donovan2da07202016-04-01 15:04:45 -0400213func (r *implementsResult) PrintPlain(printf printfFunc) {
Alan Donovan73473792016-02-11 17:57:17 -0500214 relation := "is implemented by"
215
216 meth := func(sel *types.Selection) {
217 if sel != nil {
218 printf(sel.Obj(), "\t%s method (%s).%s",
219 relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
220 }
221 }
222
223 if isInterface(r.t) {
224 if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
225 printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
226 return
227 }
228
229 if r.method == nil {
230 printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
231 } else {
232 printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
233 }
234
235 // Show concrete types (or methods) first; use two passes.
236 for i, sub := range r.to {
237 if !isInterface(sub) {
238 if r.method == nil {
239 printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
240 relation, typeKind(sub), r.qpos.typeString(sub))
241 } else {
242 meth(r.toMethod[i])
243 }
244 }
245 }
246 for i, sub := range r.to {
247 if isInterface(sub) {
248 if r.method == nil {
249 printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
250 relation, typeKind(sub), r.qpos.typeString(sub))
251 } else {
252 meth(r.toMethod[i])
253 }
254 }
255 }
256
257 relation = "implements"
258 for i, super := range r.from {
259 if r.method == nil {
260 printf(super.(*types.Named).Obj(), "\t%s %s",
261 relation, r.qpos.typeString(super))
262 } else {
263 meth(r.fromMethod[i])
264 }
265 }
266 } else {
267 relation = "implements"
268
269 if r.from != nil {
270 if r.method == nil {
271 printf(r.pos, "%s type %s",
272 typeKind(r.t), r.qpos.typeString(r.t))
273 } else {
274 printf(r.method, "concrete method %s",
275 r.qpos.objectString(r.method))
276 }
277 for i, super := range r.from {
278 if r.method == nil {
279 printf(super.(*types.Named).Obj(), "\t%s %s",
280 relation, r.qpos.typeString(super))
281 } else {
282 meth(r.fromMethod[i])
283 }
284 }
285 }
286 if r.fromPtr != nil {
287 if r.method == nil {
288 printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
289 } else {
290 // TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
291 printf(r.method, "concrete method %s",
292 r.qpos.objectString(r.method))
293 }
294
295 for i, psuper := range r.fromPtr {
296 if r.method == nil {
297 printf(psuper.(*types.Named).Obj(), "\t%s %s",
298 relation, r.qpos.typeString(psuper))
299 } else {
300 meth(r.fromPtrMethod[i])
301 }
302 }
303 } else if r.from == nil {
304 printf(r.pos, "%s type %s implements only interface{}",
305 typeKind(r.t), r.qpos.typeString(r.t))
306 }
307 }
308}
309
Alan Donovan2da07202016-04-01 15:04:45 -0400310func (r *implementsResult) JSON(fset *token.FileSet) []byte {
311 var method *serial.DescribeMethod
312 if r.method != nil {
313 method = &serial.DescribeMethod{
314 Name: r.qpos.objectString(r.method),
315 Pos: fset.Position(r.method.Pos()).String(),
316 }
317 }
318 return toJSON(&serial.Implements{
Alan Donovan73473792016-02-11 17:57:17 -0500319 T: makeImplementsType(r.t, fset),
320 AssignableTo: makeImplementsTypes(r.to, fset),
321 AssignableFrom: makeImplementsTypes(r.from, fset),
322 AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
323 AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
324 AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
325 AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
Alan Donovan2da07202016-04-01 15:04:45 -0400326 Method: method,
327 })
328
Alan Donovan73473792016-02-11 17:57:17 -0500329}
330
331func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
332 var r []serial.ImplementsType
333 for _, t := range tt {
334 r = append(r, makeImplementsType(t, fset))
335 }
336 return r
337}
338
339func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
340 var pos token.Pos
341 if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
342 pos = nt.Obj().Pos()
343 }
344 return serial.ImplementsType{
345 Name: T.String(),
346 Pos: fset.Position(pos).String(),
347 Kind: typeKind(T),
348 }
349}
350
351// typeKind returns a string describing the underlying kind of type,
352// e.g. "slice", "array", "struct".
353func typeKind(T types.Type) string {
354 s := reflect.TypeOf(T.Underlying()).String()
355 return strings.ToLower(strings.TrimPrefix(s, "*types."))
356}
357
358func isInterface(T types.Type) bool { return types.IsInterface(T) }
359
360type typesByString []types.Type
361
362func (p typesByString) Len() int { return len(p) }
363func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
364func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }