--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package analysis
+
+// This file computes the markup for information from go/types:
+// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
+// the IMPLEMENTS relation.
+//
+// IMPORTS links connect import specs to the documentation for the
+// imported package.
+//
+// RESOLUTION links referring identifiers to their defining
+// identifier, and adds tooltips for kind and type.
+//
+// METHOD SETS, size/alignment, and the IMPLEMENTS relation are
+// displayed in the lower pane when a type's defining identifier is
+// clicked.
+
+import (
+ "fmt"
+ "go/types"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "golang.org/x/tools/go/loader"
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+// TODO(adonovan): audit to make sure it's safe on ill-typed packages.
+
+// TODO(adonovan): use same Sizes as loader.Config.
+var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
+
+func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) {
+ // We must not assume the corresponding SSA packages were
+ // created (i.e. were transitively error-free).
+
+ // IMPORTS
+ for _, f := range info.Files {
+ // Package decl.
+ fi, offset := a.fileAndOffset(f.Name.Pos())
+ fi.addLink(aLink{
+ start: offset,
+ end: offset + len(f.Name.Name),
+ title: "Package docs for " + info.Pkg.Path(),
+ // TODO(adonovan): fix: we're putting the untrusted Path()
+ // into a trusted field. What's the appropriate sanitizer?
+ href: "/pkg/" + info.Pkg.Path(),
+ })
+
+ // Import specs.
+ for _, imp := range f.Imports {
+ // Remove quotes.
+ L := int(imp.End()-imp.Path.Pos()) - len(`""`)
+ path, _ := strconv.Unquote(imp.Path.Value)
+ fi, offset := a.fileAndOffset(imp.Path.Pos())
+ fi.addLink(aLink{
+ start: offset + 1,
+ end: offset + 1 + L,
+ title: "Package docs for " + path,
+ // TODO(adonovan): fix: we're putting the untrusted path
+ // into a trusted field. What's the appropriate sanitizer?
+ href: "/pkg/" + path,
+ })
+ }
+ }
+
+ // RESOLUTION
+ qualifier := types.RelativeTo(info.Pkg)
+ for id, obj := range info.Uses {
+ // Position of the object definition.
+ pos := obj.Pos()
+ Len := len(obj.Name())
+
+ // Correct the position for non-renaming import specs.
+ // import "sync/atomic"
+ // ^^^^^^^^^^^
+ if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() {
+ // Assume this is a non-renaming import.
+ // NB: not true for degenerate renamings: `import foo "foo"`.
+ pos++
+ Len = len(obj.Imported().Path())
+ }
+
+ if obj.Pkg() == nil {
+ continue // don't mark up built-ins.
+ }
+
+ fi, offset := a.fileAndOffset(id.NamePos)
+ fi.addLink(aLink{
+ start: offset,
+ end: offset + len(id.Name),
+ title: types.ObjectString(obj, qualifier),
+ href: a.posURL(pos, Len),
+ })
+ }
+
+ // IMPLEMENTS & METHOD SETS
+ for _, obj := range info.Defs {
+ if obj, ok := obj.(*types.TypeName); ok {
+ if named, ok := obj.Type().(*types.Named); ok {
+ a.namedType(named, implements)
+ }
+ }
+ }
+}
+
+func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
+ obj := T.Obj()
+ qualifier := types.RelativeTo(obj.Pkg())
+ v := &TypeInfoJSON{
+ Name: obj.Name(),
+ Size: sizes.Sizeof(T),
+ Align: sizes.Alignof(T),
+ Methods: []anchorJSON{}, // (JS wants non-nil)
+ }
+
+ // addFact adds the fact "is implemented by T" (by) or
+ // "implements T" (!by) to group.
+ addFact := func(group *implGroupJSON, T types.Type, by bool) {
+ Tobj := deref(T).(*types.Named).Obj()
+ var byKind string
+ if by {
+ // Show underlying kind of implementing type,
+ // e.g. "slice", "array", "struct".
+ s := reflect.TypeOf(T.Underlying()).String()
+ byKind = strings.ToLower(strings.TrimPrefix(s, "*types."))
+ }
+ group.Facts = append(group.Facts, implFactJSON{
+ ByKind: byKind,
+ Other: anchorJSON{
+ Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
+ Text: types.TypeString(T, qualifier),
+ },
+ })
+ }
+
+ // IMPLEMENTS
+ if r, ok := implements[T]; ok {
+ if isInterface(T) {
+ // "T is implemented by <conc>" ...
+ // "T is implemented by <iface>"...
+ // "T implements <iface>"...
+ group := implGroupJSON{
+ Descr: types.TypeString(T, qualifier),
+ }
+ // Show concrete types first; use two passes.
+ for _, sub := range r.to {
+ if !isInterface(sub) {
+ addFact(&group, sub, true)
+ }
+ }
+ for _, sub := range r.to {
+ if isInterface(sub) {
+ addFact(&group, sub, true)
+ }
+ }
+ for _, super := range r.from {
+ addFact(&group, super, false)
+ }
+ v.ImplGroups = append(v.ImplGroups, group)
+ } else {
+ // T is concrete.
+ if r.from != nil {
+ // "T implements <iface>"...
+ group := implGroupJSON{
+ Descr: types.TypeString(T, qualifier),
+ }
+ for _, super := range r.from {
+ addFact(&group, super, false)
+ }
+ v.ImplGroups = append(v.ImplGroups, group)
+ }
+ if r.fromPtr != nil {
+ // "*C implements <iface>"...
+ group := implGroupJSON{
+ Descr: "*" + types.TypeString(T, qualifier),
+ }
+ for _, psuper := range r.fromPtr {
+ addFact(&group, psuper, false)
+ }
+ v.ImplGroups = append(v.ImplGroups, group)
+ }
+ }
+ }
+
+ // METHOD SETS
+ for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) {
+ meth := sel.Obj().(*types.Func)
+ pos := meth.Pos() // may be 0 for error.Error
+ v.Methods = append(v.Methods, anchorJSON{
+ Href: a.posURL(pos, len(meth.Name())),
+ Text: types.SelectionString(sel, qualifier),
+ })
+ }
+
+ // Since there can be many specs per decl, we
+ // can't attach the link to the keyword 'type'
+ // (as we do with 'func'); we use the Ident.
+ fi, offset := a.fileAndOffset(obj.Pos())
+ fi.addLink(aLink{
+ start: offset,
+ end: offset + len(obj.Name()),
+ title: fmt.Sprintf("type info for %s", obj.Name()),
+ onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)),
+ })
+
+ // Add info for exported package-level types to the package info.
+ if obj.Exported() && isPackageLevel(obj) {
+ // TODO(adonovan): Path is not unique!
+ // It is possible to declare a non-test package called x_test.
+ a.result.pkgInfo(obj.Pkg().Path()).addType(v)
+ }
+}
+
+// -- utilities --------------------------------------------------------
+
+func isInterface(T types.Type) bool { return types.IsInterface(T) }
+
+// deref returns a pointer's element type; otherwise it returns typ.
+func deref(typ types.Type) types.Type {
+ if p, ok := typ.Underlying().(*types.Pointer); ok {
+ return p.Elem()
+ }
+ return typ
+}
+
+// isPackageLevel reports whether obj is a package-level object.
+func isPackageLevel(obj types.Object) bool {
+ return obj.Pkg().Scope().Lookup(obj.Name()) == obj
+}