github.com/v2fly/tools@v0.100.0/godoc/analysis/typeinfo.go (about)

     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  
     5  package 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  
    21  import (
    22  	"fmt"
    23  	"go/types"
    24  	"reflect"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/v2fly/tools/go/loader"
    29  	"github.com/v2fly/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.
    35  var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
    36  
    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).
    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  
   111  func (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  
   221  func isInterface(T types.Type) bool { return types.IsInterface(T) }
   222  
   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 {
   226  		return p.Elem()
   227  	}
   228  	return typ
   229  }
   230  
   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
   234  }