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.
4
5package analysis
6
7// This file computes the markup for information from go/types:
8// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
9// the IMPLEMENTS relation.
10//
11// IMPORTS links connect import specs to the documentation for the
12// imported package.
13//
14// RESOLUTION links referring identifiers to their defining
15// identifier, and adds tooltips for kind and type.
16//
17// METHOD SETS, size/alignment, and the IMPLEMENTS relation are
18// displayed in the lower pane when a type's defining identifier is
19// clicked.
20
21import (
22	"fmt"
23	"go/types"
24	"reflect"
25	"strconv"
26	"strings"
27
28	"golang.org/x/tools/go/loader"
29	"golang.org/x/tools/go/types/typeutil"
30)
31
32// TODO(adonovan): audit to make sure it's safe on ill-typed packages.
33
34// TODO(adonovan): use same Sizes as loader.Config.
35var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
36
37func (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).
40
41	// IMPORTS
42	for _, f := range info.Files {
43		// Package decl.
44		fi, offset := a.fileAndOffset(f.Name.Pos())
45		fi.addLink(aLink{
46			start: offset,
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(),
52		})
53
54		// Import specs.
55		for _, imp := range f.Imports {
56			// Remove quotes.
57			L := int(imp.End()-imp.Path.Pos()) - len(`""`)
58			path, _ := strconv.Unquote(imp.Path.Value)
59			fi, offset := a.fileAndOffset(imp.Path.Pos())
60			fi.addLink(aLink{
61				start: offset + 1,
62				end:   offset + 1 + L,
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?
66				href: "/pkg/" + path,
67			})
68		}
69	}
70
71	// RESOLUTION
72	qualifier := types.RelativeTo(info.Pkg)
73	for id, obj := range info.Uses {
74		// Position of the object definition.
75		pos := obj.Pos()
76		Len := len(obj.Name())
77
78		// Correct the position for non-renaming import specs.
79		//  import "sync/atomic"
80		//          ^^^^^^^^^^^
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"`.
84			pos++
85			Len = len(obj.Imported().Path())
86		}
87
88		if obj.Pkg() == nil {
89			continue // don't mark up built-ins.
90		}
91
92		fi, offset := a.fileAndOffset(id.NamePos)
93		fi.addLink(aLink{
94			start: offset,
95			end:   offset + len(id.Name),
96			title: types.ObjectString(obj, qualifier),
97			href:  a.posURL(pos, Len),
98		})
99	}
100
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)
106			}
107		}
108	}
109}
110
111func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
112	obj := T.Obj()
113	qualifier := types.RelativeTo(obj.Pkg())
114	v := &TypeInfoJSON{
115		Name:    obj.Name(),
116		Size:    sizes.Sizeof(T),
117		Align:   sizes.Alignof(T),
118		Methods: []anchorJSON{}, // (JS wants non-nil)
119	}
120
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()
125		var byKind string
126		if by {
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."))
131		}
132		group.Facts = append(group.Facts, implFactJSON{
133			ByKind: byKind,
134			Other: anchorJSON{
135				Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
136				Text: types.TypeString(T, qualifier),
137			},
138		})
139	}
140
141	// IMPLEMENTS
142	if r, ok := implements[T]; ok {
143		if isInterface(T) {
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),
149			}
150			// Show concrete types first; use two passes.
151			for _, sub := range r.to {
152				if !isInterface(sub) {
153					addFact(&group, sub, true)
154				}
155			}
156			for _, sub := range r.to {
157				if isInterface(sub) {
158					addFact(&group, sub, true)
159				}
160			}
161			for _, super := range r.from {
162				addFact(&group, super, false)
163			}
164			v.ImplGroups = append(v.ImplGroups, group)
165		} else {
166			// T is concrete.
167			if r.from != nil {
168				// "T implements <iface>"...
169				group := implGroupJSON{
170					Descr: types.TypeString(T, qualifier),
171				}
172				for _, super := range r.from {
173					addFact(&group, super, false)
174				}
175				v.ImplGroups = append(v.ImplGroups, group)
176			}
177			if r.fromPtr != nil {
178				// "*C implements <iface>"...
179				group := implGroupJSON{
180					Descr: "*" + types.TypeString(T, qualifier),
181				}
182				for _, psuper := range r.fromPtr {
183					addFact(&group, psuper, false)
184				}
185				v.ImplGroups = append(v.ImplGroups, group)
186			}
187		}
188	}
189
190	// METHOD SETS
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),
197		})
198	}
199
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())
204	fi.addLink(aLink{
205		start:   offset,
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)),
209	})
210
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)
216	}
217}
218
219// -- utilities --------------------------------------------------------
220
221func isInterface(T types.Type) bool { return types.IsInterface(T) }
222
223// deref returns a pointer's element type; otherwise it returns typ.
224func deref(typ types.Type) types.Type {
225	if p, ok := typ.Underlying().(*types.Pointer); ok {
226		return p.Elem()
227	}
228	return typ
229}
230
231// isPackageLevel reports whether obj is a package-level object.
232func isPackageLevel(obj types.Object) bool {
233	return obj.Pkg().Scope().Lookup(obj.Name()) == obj
234}
235