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