1 // Copyright 2014 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.
7 // This file computes the markup for information from go/types:
8 // IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
9 // the IMPLEMENTS relation.
11 // IMPORTS links connect import specs to the documentation for the
14 // RESOLUTION links referring identifiers to their defining
15 // identifier, and adds tooltips for kind and type.
17 // METHOD SETS, size/alignment, and the IMPLEMENTS relation are
18 // displayed in the lower pane when a type's defining identifier is
28 "golang.org/x/tools/go/loader"
29 "golang.org/x/tools/go/types/typeutil"
32 // TODO(adonovan): audit to make sure it's safe on ill-typed packages.
34 // TODO(adonovan): use same Sizes as loader.Config.
35 var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
37 func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) {
38 // We must not assume the corresponding SSA packages were
39 // created (i.e. were transitively error-free).
42 for _, f := range info.Files {
44 fi, offset := a.fileAndOffset(f.Name.Pos())
47 end: offset + len(f.Name.Name),
48 title: "Package docs for " + info.Pkg.Path(),
49 // TODO(adonovan): fix: we're putting the untrusted Path()
50 // into a trusted field. What's the appropriate sanitizer?
51 href: "/pkg/" + info.Pkg.Path(),
55 for _, imp := range f.Imports {
57 L := int(imp.End()-imp.Path.Pos()) - len(`""`)
58 path, _ := strconv.Unquote(imp.Path.Value)
59 fi, offset := a.fileAndOffset(imp.Path.Pos())
63 title: "Package docs for " + path,
64 // TODO(adonovan): fix: we're putting the untrusted path
65 // into a trusted field. What's the appropriate sanitizer?
72 qualifier := types.RelativeTo(info.Pkg)
73 for id, obj := range info.Uses {
74 // Position of the object definition.
76 Len := len(obj.Name())
78 // Correct the position for non-renaming import specs.
79 // import "sync/atomic"
81 if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() {
82 // Assume this is a non-renaming import.
83 // NB: not true for degenerate renamings: `import foo "foo"`.
85 Len = len(obj.Imported().Path())
89 continue // don't mark up built-ins.
92 fi, offset := a.fileAndOffset(id.NamePos)
95 end: offset + len(id.Name),
96 title: types.ObjectString(obj, qualifier),
97 href: a.posURL(pos, Len),
101 // IMPLEMENTS & METHOD SETS
102 for _, obj := range info.Defs {
103 if obj, ok := obj.(*types.TypeName); ok {
104 if named, ok := obj.Type().(*types.Named); ok {
105 a.namedType(named, implements)
111 func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
113 qualifier := types.RelativeTo(obj.Pkg())
116 Size: sizes.Sizeof(T),
117 Align: sizes.Alignof(T),
118 Methods: []anchorJSON{}, // (JS wants non-nil)
121 // addFact adds the fact "is implemented by T" (by) or
122 // "implements T" (!by) to group.
123 addFact := func(group *implGroupJSON, T types.Type, by bool) {
124 Tobj := deref(T).(*types.Named).Obj()
127 // Show underlying kind of implementing type,
128 // e.g. "slice", "array", "struct".
129 s := reflect.TypeOf(T.Underlying()).String()
130 byKind = strings.ToLower(strings.TrimPrefix(s, "*types."))
132 group.Facts = append(group.Facts, implFactJSON{
135 Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
136 Text: types.TypeString(T, qualifier),
142 if r, ok := implements[T]; ok {
144 // "T is implemented by <conc>" ...
145 // "T is implemented by <iface>"...
146 // "T implements <iface>"...
147 group := implGroupJSON{
148 Descr: types.TypeString(T, qualifier),
150 // Show concrete types first; use two passes.
151 for _, sub := range r.to {
152 if !isInterface(sub) {
153 addFact(&group, sub, true)
156 for _, sub := range r.to {
157 if isInterface(sub) {
158 addFact(&group, sub, true)
161 for _, super := range r.from {
162 addFact(&group, super, false)
164 v.ImplGroups = append(v.ImplGroups, group)
168 // "T implements <iface>"...
169 group := implGroupJSON{
170 Descr: types.TypeString(T, qualifier),
172 for _, super := range r.from {
173 addFact(&group, super, false)
175 v.ImplGroups = append(v.ImplGroups, group)
177 if r.fromPtr != nil {
178 // "*C implements <iface>"...
179 group := implGroupJSON{
180 Descr: "*" + types.TypeString(T, qualifier),
182 for _, psuper := range r.fromPtr {
183 addFact(&group, psuper, false)
185 v.ImplGroups = append(v.ImplGroups, group)
191 for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) {
192 meth := sel.Obj().(*types.Func)
193 pos := meth.Pos() // may be 0 for error.Error
194 v.Methods = append(v.Methods, anchorJSON{
195 Href: a.posURL(pos, len(meth.Name())),
196 Text: types.SelectionString(sel, qualifier),
200 // Since there can be many specs per decl, we
201 // can't attach the link to the keyword 'type'
202 // (as we do with 'func'); we use the Ident.
203 fi, offset := a.fileAndOffset(obj.Pos())
206 end: offset + len(obj.Name()),
207 title: fmt.Sprintf("type info for %s", obj.Name()),
208 onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)),
211 // Add info for exported package-level types to the package info.
212 if obj.Exported() && isPackageLevel(obj) {
213 // TODO(adonovan): Path is not unique!
214 // It is possible to declare a non-test package called x_test.
215 a.result.pkgInfo(obj.Pkg().Path()).addType(v)
219 // -- utilities --------------------------------------------------------
221 func isInterface(T types.Type) bool { return types.IsInterface(T) }
223 // deref returns a pointer's element type; otherwise it returns typ.
224 func deref(typ types.Type) types.Type {
225 if p, ok := typ.Underlying().(*types.Pointer); ok {
231 // isPackageLevel reports whether obj is a package-level object.
232 func isPackageLevel(obj types.Object) bool {
233 return obj.Pkg().Scope().Lookup(obj.Name()) == obj