// 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 " ... // "T is implemented by "... // "T implements "... 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 "... 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 "... 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 }