GoPLS Viewer

Home|gopls/cmd/guru/implements.go
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
5package main
6
7import (
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.
27func implements(q *Queryerror {
28    lconf := loader.Config{Buildq.Build}
29    allowErrors(&lconf)
30
31    qpkgerr := 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(&lconfq.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    lprogerr := lconf.Load()
62    if err != nil {
63        return err
64    }
65
66    qposerr := parseQueryPos(lprogq.Posfalse)
67    if err != nil {
68        return err
69    }
70
71    // Find the selected type.
72    pathaction := findInterestingNode(qpos.infoqpos.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 idok := path[0].(*ast.Ident); ok {
81            if objok := 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 objok := obj.(*types.TypeName); ok && !isAlias(obj) {
111                if namedok := obj.Type().(*types.Named); ok {
112                    allNamed = append(allNamednamed)
113                }
114            }
115        }
116    }
117    allNamed = append(allNamedtypes.Universe.Lookup("error").Type().(*types.Named))
118
119    var msets typeutil.MethodSetCache
120
121    // Test each named type.
122    var tofromfromPtr []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(TU) {
135                    if types.AssignableTo(UT) {
136                        to = append(toU)
137                    }
138                    if types.AssignableTo(TU) {
139                        from = append(fromU)
140                    }
141                }
142            } else {
143                // T interface, U concrete
144                if types.AssignableTo(UT) {
145                    to = append(toU)
146                } else if pU := types.NewPointer(U); types.AssignableTo(pUT) {
147                    to = append(topU)
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(TU) {
157                from = append(fromU)
158            } else if pT := types.NewPointer(T); types.AssignableTo(pTU) {
159                fromPtr = append(fromPtrU)
160            }
161        }
162    }
163
164    var pos interface{} = qpos
165    if ntok := 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 toMethodfromMethodfromPtrMethod []*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        qposTpostofromfromPtrmethodtoMethodfromMethodfromPtrMethod,
192    })
193    return nil
194}
195
196type 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
212func (r *implementsResultPrintPlain(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                relationr.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 isub := 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                        relationtypeKind(sub), r.qpos.typeString(sub))
240                } else {
241                    meth(r.toMethod[i])
242                }
243            }
244        }
245        for isub := range r.to {
246            if isInterface(sub) {
247                if r.method == nil {
248                    printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
249                        relationtypeKind(sub), r.qpos.typeString(sub))
250                } else {
251                    meth(r.toMethod[i])
252                }
253            }
254        }
255
256        relation = "implements"
257        for isuper := range r.from {
258            if r.method == nil {
259                printf(super.(*types.Named).Obj(), "\t%s %s",
260                    relationr.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 isuper := range r.from {
277                if r.method == nil {
278                    printf(super.(*types.Named).Obj(), "\t%s %s",
279                        relationr.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 ipsuper := range r.fromPtr {
295                if r.method == nil {
296                    printf(psuper.(*types.Named).Obj(), "\t%s %s",
297                        relationr.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
309func (r *implementsResultJSON(fset *token.FileSet) []byte {
310    var method *serial.DescribeMethod
311    if r.method != nil {
312        method = &serial.DescribeMethod{
313            Namer.qpos.objectString(r.method),
314            Pos:  fset.Position(r.method.Pos()).String(),
315        }
316    }
317    return toJSON(&serial.Implements{
318        T:                       makeImplementsType(r.tfset),
319        AssignableTo:            makeImplementsTypes(r.tofset),
320        AssignableFrom:          makeImplementsTypes(r.fromfset),
321        AssignableFromPtr:       makeImplementsTypes(r.fromPtrfset),
322        AssignableToMethod:      methodsToSerial(r.qpos.info.Pkgr.toMethodfset),
323        AssignableFromMethod:    methodsToSerial(r.qpos.info.Pkgr.fromMethodfset),
324        AssignableFromPtrMethodmethodsToSerial(r.qpos.info.Pkgr.fromPtrMethodfset),
325        Method:                  method,
326    })
327
328}
329
330func makeImplementsTypes(tt []types.Typefset *token.FileSet) []serial.ImplementsType {
331    var r []serial.ImplementsType
332    for _t := range tt {
333        r = append(rmakeImplementsType(tfset))
334    }
335    return r
336}
337
338func makeImplementsType(T types.Typefset *token.FileSetserial.ImplementsType {
339    var pos token.Pos
340    if ntok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
341        pos = nt.Obj().Pos()
342    }
343    return serial.ImplementsType{
344        NameT.String(),
345        Pos:  fset.Position(pos).String(),
346        KindtypeKind(T),
347    }
348}
349
350// typeKind returns a string describing the underlying kind of type,
351// e.g. "slice", "array", "struct".
352func typeKind(T types.Typestring {
353    s := reflect.TypeOf(T.Underlying()).String()
354    return strings.ToLower(strings.TrimPrefix(s"*types."))
355}
356
357func isInterface(T types.Typebool { return types.IsInterface(T) }
358
359type typesByString []types.Type
360
361func (p typesByStringLen() int           { return len(p) }
362func (p typesByStringLess(ij intbool { return p[i].String() < p[j].String() }
363func (p typesByStringSwap(ij int)      { p[i], p[j] = p[j], p[i] }
364
MembersX
implements.BlockStmt.RangeStmt_4579.t
implementsResult.fromPtr
implementsResult.method
implementsResult.toMethod
implements.BlockStmt._
implements.qpos
implementsResult.PrintPlain.BlockStmt.BlockStmt.RangeStmt_7916.psuper
typesByString.Len.p
implementsResult.fromPtrMethod
implementsResult.PrintPlain.BlockStmt.RangeStmt_6714.i
implementsResult.JSON
makeImplementsType
typesByString.Len
implements.lprog
implements.RangeStmt_3282.BlockStmt.BlockStmt.BlockStmt.pU
implements.RangeStmt_3282.U
implements.RangeStmt_3282.BlockStmt.BlockStmt.pT
implements.toMethod
implements.BlockStmt.RangeStmt_4702.t
implementsResult.JSON.fset
makeImplementsTypes
implements.path
implements.action
implementsResult.t
implementsResult.PrintPlain.BlockStmt.BlockStmt.RangeStmt_7916.i
implements.q
implements.to
implements.fromMethod
implementsResult.pos
implementsResult.from
implementsResult.PrintPlain.printf
makeImplementsTypes.fset
makeImplementsType.T
implements.err
implements.T
typesByString.Swap.j
makeImplementsType.pos
typesByString.Less
implements.fromPtrMethod
implements.BlockStmt.RangeStmt_4831.t
implementsResult
implementsResult.PrintPlain.BlockStmt.BlockStmt.RangeStmt_7435.i
implements.BlockStmt.err
implements.BlockStmt.rev
implements.fromPtr
implementsResult.qpos
implementsResult.PrintPlain.BlockStmt.RangeStmt_6985.super
implementsResult.JSON.r
makeImplementsType.fset
typeKind.s
implements.lconf
implements.msets
typesByString.Less.j
implementsResult.PrintPlain.relation
makeImplementsTypes.tt
makeImplementsTypes.r
typeKind
typesByString.Swap.p
typesByString.Swap.i
implements.method
implements.RangeStmt_2852.info
typesByString.Less.i
reflect
implementsResult.PrintPlain.BlockStmt.RangeStmt_6462.sub
implements.BlockStmt.RangeStmt_1250.path
implementsResult.JSON.method
implements
implementsResult.fromMethod
implementsResult.PrintPlain.BlockStmt.RangeStmt_6462.i
implementsResult.PrintPlain.BlockStmt.RangeStmt_6985.i
typesByString.Less.p
implements.RangeStmt_2852.BlockStmt.RangeStmt_2895.obj
implements.from
implementsResult.PrintPlain
isInterface.T
typesByString
implements.qpkg
implementsResult.PrintPlain.r
implementsResult.to
makeImplementsTypes.RangeStmt_9203.t
typeKind.T
isInterface
typesByString.Swap
importgraph
implements.BlockStmt.BlockStmt.BlockStmt.recv
implementsResult.PrintPlain.BlockStmt.BlockStmt.RangeStmt_7435.super
implements.allNamed
implementsResult.PrintPlain.BlockStmt.RangeStmt_6714.sub
Members
X