...
Run Format

Source file src/cmd/api/goapi.go

Documentation: cmd/api

     1  // Copyright 2011 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  // Binary api computes the exported API of a set of Go packages.
     6  package main
     7  
     8  import (
     9  	"bufio"
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/build"
    15  	"go/parser"
    16  	"go/token"
    17  	"go/types"
    18  	"io"
    19  	"io/ioutil"
    20  	"log"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"regexp"
    25  	"runtime"
    26  	"sort"
    27  	"strings"
    28  )
    29  
    30  func goCmd() string {
    31  	var exeSuffix string
    32  	if runtime.GOOS == "windows" {
    33  		exeSuffix = ".exe"
    34  	}
    35  	path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
    36  	if _, err := os.Stat(path); err == nil {
    37  		return path
    38  	}
    39  	return "go"
    40  }
    41  
    42  // Flags
    43  var (
    44  	checkFile  = flag.String("c", "", "optional comma-separated filename(s) to check API against")
    45  	allowNew   = flag.Bool("allow_new", true, "allow API additions")
    46  	exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
    47  	nextFile   = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
    48  	verbose    = flag.Bool("v", false, "verbose debugging")
    49  	forceCtx   = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
    50  )
    51  
    52  // contexts are the default contexts which are scanned, unless
    53  // overridden by the -contexts flag.
    54  var contexts = []*build.Context{
    55  	{GOOS: "linux", GOARCH: "386", CgoEnabled: true},
    56  	{GOOS: "linux", GOARCH: "386"},
    57  	{GOOS: "linux", GOARCH: "amd64", CgoEnabled: true},
    58  	{GOOS: "linux", GOARCH: "amd64"},
    59  	{GOOS: "linux", GOARCH: "arm", CgoEnabled: true},
    60  	{GOOS: "linux", GOARCH: "arm"},
    61  	{GOOS: "darwin", GOARCH: "386", CgoEnabled: true},
    62  	{GOOS: "darwin", GOARCH: "386"},
    63  	{GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true},
    64  	{GOOS: "darwin", GOARCH: "amd64"},
    65  	{GOOS: "windows", GOARCH: "amd64"},
    66  	{GOOS: "windows", GOARCH: "386"},
    67  	{GOOS: "freebsd", GOARCH: "386", CgoEnabled: true},
    68  	{GOOS: "freebsd", GOARCH: "386"},
    69  	{GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true},
    70  	{GOOS: "freebsd", GOARCH: "amd64"},
    71  	{GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true},
    72  	{GOOS: "freebsd", GOARCH: "arm"},
    73  	{GOOS: "netbsd", GOARCH: "386", CgoEnabled: true},
    74  	{GOOS: "netbsd", GOARCH: "386"},
    75  	{GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true},
    76  	{GOOS: "netbsd", GOARCH: "amd64"},
    77  	{GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true},
    78  	{GOOS: "netbsd", GOARCH: "arm"},
    79  	{GOOS: "openbsd", GOARCH: "386", CgoEnabled: true},
    80  	{GOOS: "openbsd", GOARCH: "386"},
    81  	{GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true},
    82  	{GOOS: "openbsd", GOARCH: "amd64"},
    83  }
    84  
    85  func contextName(c *build.Context) string {
    86  	s := c.GOOS + "-" + c.GOARCH
    87  	if c.CgoEnabled {
    88  		return s + "-cgo"
    89  	}
    90  	return s
    91  }
    92  
    93  func parseContext(c string) *build.Context {
    94  	parts := strings.Split(c, "-")
    95  	if len(parts) < 2 {
    96  		log.Fatalf("bad context: %q", c)
    97  	}
    98  	bc := &build.Context{
    99  		GOOS:   parts[0],
   100  		GOARCH: parts[1],
   101  	}
   102  	if len(parts) == 3 {
   103  		if parts[2] == "cgo" {
   104  			bc.CgoEnabled = true
   105  		} else {
   106  			log.Fatalf("bad context: %q", c)
   107  		}
   108  	}
   109  	return bc
   110  }
   111  
   112  func setContexts() {
   113  	contexts = []*build.Context{}
   114  	for _, c := range strings.Split(*forceCtx, ",") {
   115  		contexts = append(contexts, parseContext(c))
   116  	}
   117  }
   118  
   119  var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
   120  
   121  func main() {
   122  	flag.Parse()
   123  
   124  	if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
   125  		if *nextFile != "" {
   126  			fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
   127  			*nextFile = ""
   128  		}
   129  	}
   130  
   131  	if *forceCtx != "" {
   132  		setContexts()
   133  	}
   134  	for _, c := range contexts {
   135  		c.Compiler = build.Default.Compiler
   136  	}
   137  
   138  	var pkgNames []string
   139  	if flag.NArg() > 0 {
   140  		pkgNames = flag.Args()
   141  	} else {
   142  		stds, err := exec.Command(goCmd(), "list", "std").Output()
   143  		if err != nil {
   144  			log.Fatal(err)
   145  		}
   146  		for _, pkg := range strings.Fields(string(stds)) {
   147  			if !internalPkg.MatchString(pkg) {
   148  				pkgNames = append(pkgNames, pkg)
   149  			}
   150  		}
   151  	}
   152  
   153  	var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
   154  	for _, context := range contexts {
   155  		w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
   156  
   157  		for _, name := range pkgNames {
   158  			// Vendored packages do not contribute to our
   159  			// public API surface.
   160  			if strings.HasPrefix(name, "vendor/") {
   161  				continue
   162  			}
   163  			// - Package "unsafe" contains special signatures requiring
   164  			//   extra care when printing them - ignore since it is not
   165  			//   going to change w/o a language change.
   166  			// - We don't care about the API of commands.
   167  			if name != "unsafe" && !strings.HasPrefix(name, "cmd/") {
   168  				if name == "runtime/cgo" && !context.CgoEnabled {
   169  					// w.Import(name) will return nil
   170  					continue
   171  				}
   172  				pkg, _ := w.Import(name)
   173  				w.export(pkg)
   174  			}
   175  		}
   176  
   177  		ctxName := contextName(context)
   178  		for _, f := range w.Features() {
   179  			if featureCtx[f] == nil {
   180  				featureCtx[f] = make(map[string]bool)
   181  			}
   182  			featureCtx[f][ctxName] = true
   183  		}
   184  	}
   185  
   186  	var features []string
   187  	for f, cmap := range featureCtx {
   188  		if len(cmap) == len(contexts) {
   189  			features = append(features, f)
   190  			continue
   191  		}
   192  		comma := strings.Index(f, ",")
   193  		for cname := range cmap {
   194  			f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
   195  			features = append(features, f2)
   196  		}
   197  	}
   198  
   199  	fail := false
   200  	defer func() {
   201  		if fail {
   202  			os.Exit(1)
   203  		}
   204  	}()
   205  
   206  	bw := bufio.NewWriter(os.Stdout)
   207  	defer bw.Flush()
   208  
   209  	if *checkFile == "" {
   210  		sort.Strings(features)
   211  		for _, f := range features {
   212  			fmt.Fprintln(bw, f)
   213  		}
   214  		return
   215  	}
   216  
   217  	var required []string
   218  	for _, file := range strings.Split(*checkFile, ",") {
   219  		required = append(required, fileFeatures(file)...)
   220  	}
   221  	optional := fileFeatures(*nextFile)
   222  	exception := fileFeatures(*exceptFile)
   223  	fail = !compareAPI(bw, features, required, optional, exception,
   224  		*allowNew && strings.Contains(runtime.Version(), "devel"))
   225  }
   226  
   227  // export emits the exported package features.
   228  func (w *Walker) export(pkg *types.Package) {
   229  	if *verbose {
   230  		log.Println(pkg)
   231  	}
   232  	pop := w.pushScope("pkg " + pkg.Path())
   233  	w.current = pkg
   234  	scope := pkg.Scope()
   235  	for _, name := range scope.Names() {
   236  		if ast.IsExported(name) {
   237  			w.emitObj(scope.Lookup(name))
   238  		}
   239  	}
   240  	pop()
   241  }
   242  
   243  func set(items []string) map[string]bool {
   244  	s := make(map[string]bool)
   245  	for _, v := range items {
   246  		s[v] = true
   247  	}
   248  	return s
   249  }
   250  
   251  var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
   252  
   253  func featureWithoutContext(f string) string {
   254  	if !strings.Contains(f, "(") {
   255  		return f
   256  	}
   257  	return spaceParensRx.ReplaceAllString(f, "")
   258  }
   259  
   260  func compareAPI(w io.Writer, features, required, optional, exception []string, allowAdd bool) (ok bool) {
   261  	ok = true
   262  
   263  	optionalSet := set(optional)
   264  	exceptionSet := set(exception)
   265  	featureSet := set(features)
   266  
   267  	sort.Strings(features)
   268  	sort.Strings(required)
   269  
   270  	take := func(sl *[]string) string {
   271  		s := (*sl)[0]
   272  		*sl = (*sl)[1:]
   273  		return s
   274  	}
   275  
   276  	for len(required) > 0 || len(features) > 0 {
   277  		switch {
   278  		case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
   279  			feature := take(&required)
   280  			if exceptionSet[feature] {
   281  				// An "unfortunate" case: the feature was once
   282  				// included in the API (e.g. go1.txt), but was
   283  				// subsequently removed. These are already
   284  				// acknowledged by being in the file
   285  				// "api/except.txt". No need to print them out
   286  				// here.
   287  			} else if featureSet[featureWithoutContext(feature)] {
   288  				// okay.
   289  			} else {
   290  				fmt.Fprintf(w, "-%s\n", feature)
   291  				ok = false // broke compatibility
   292  			}
   293  		case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
   294  			newFeature := take(&features)
   295  			if optionalSet[newFeature] {
   296  				// Known added feature to the upcoming release.
   297  				// Delete it from the map so we can detect any upcoming features
   298  				// which were never seen.  (so we can clean up the nextFile)
   299  				delete(optionalSet, newFeature)
   300  			} else {
   301  				fmt.Fprintf(w, "+%s\n", newFeature)
   302  				if !allowAdd {
   303  					ok = false // we're in lock-down mode for next release
   304  				}
   305  			}
   306  		default:
   307  			take(&required)
   308  			take(&features)
   309  		}
   310  	}
   311  
   312  	// In next file, but not in API.
   313  	var missing []string
   314  	for feature := range optionalSet {
   315  		missing = append(missing, feature)
   316  	}
   317  	sort.Strings(missing)
   318  	for _, feature := range missing {
   319  		fmt.Fprintf(w, "±%s\n", feature)
   320  	}
   321  	return
   322  }
   323  
   324  func fileFeatures(filename string) []string {
   325  	if filename == "" {
   326  		return nil
   327  	}
   328  	bs, err := ioutil.ReadFile(filename)
   329  	if err != nil {
   330  		log.Fatalf("Error reading file %s: %v", filename, err)
   331  	}
   332  	lines := strings.Split(string(bs), "\n")
   333  	var nonblank []string
   334  	for _, line := range lines {
   335  		line = strings.TrimSpace(line)
   336  		if line != "" && !strings.HasPrefix(line, "#") {
   337  			nonblank = append(nonblank, line)
   338  		}
   339  	}
   340  	return nonblank
   341  }
   342  
   343  var fset = token.NewFileSet()
   344  
   345  type Walker struct {
   346  	context  *build.Context
   347  	root     string
   348  	scope    []string
   349  	current  *types.Package
   350  	features map[string]bool           // set
   351  	imported map[string]*types.Package // packages already imported
   352  }
   353  
   354  func NewWalker(context *build.Context, root string) *Walker {
   355  	return &Walker{
   356  		context:  context,
   357  		root:     root,
   358  		features: map[string]bool{},
   359  		imported: map[string]*types.Package{"unsafe": types.Unsafe},
   360  	}
   361  }
   362  
   363  func (w *Walker) Features() (fs []string) {
   364  	for f := range w.features {
   365  		fs = append(fs, f)
   366  	}
   367  	sort.Strings(fs)
   368  	return
   369  }
   370  
   371  var parsedFileCache = make(map[string]*ast.File)
   372  
   373  func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
   374  	filename := filepath.Join(dir, file)
   375  	if f := parsedFileCache[filename]; f != nil {
   376  		return f, nil
   377  	}
   378  
   379  	f, err := parser.ParseFile(fset, filename, nil, 0)
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	parsedFileCache[filename] = f
   384  
   385  	return f, nil
   386  }
   387  
   388  // The package cache doesn't operate correctly in rare (so far artificial)
   389  // circumstances (issue 8425). Disable before debugging non-obvious errors
   390  // from the type-checker.
   391  const usePkgCache = true
   392  
   393  var (
   394  	pkgCache = map[string]*types.Package{} // map tagKey to package
   395  	pkgTags  = map[string][]string{}       // map import dir to list of relevant tags
   396  )
   397  
   398  // tagKey returns the tag-based key to use in the pkgCache.
   399  // It is a comma-separated string; the first part is dir, the rest tags.
   400  // The satisfied tags are derived from context but only those that
   401  // matter (the ones listed in the tags argument) are used.
   402  // The tags list, which came from go/build's Package.AllTags,
   403  // is known to be sorted.
   404  func tagKey(dir string, context *build.Context, tags []string) string {
   405  	ctags := map[string]bool{
   406  		context.GOOS:   true,
   407  		context.GOARCH: true,
   408  	}
   409  	if context.CgoEnabled {
   410  		ctags["cgo"] = true
   411  	}
   412  	for _, tag := range context.BuildTags {
   413  		ctags[tag] = true
   414  	}
   415  	// TODO: ReleaseTags (need to load default)
   416  	key := dir
   417  	for _, tag := range tags {
   418  		if ctags[tag] {
   419  			key += "," + tag
   420  		}
   421  	}
   422  	return key
   423  }
   424  
   425  // Importing is a sentinel taking the place in Walker.imported
   426  // for a package that is in the process of being imported.
   427  var importing types.Package
   428  
   429  func (w *Walker) Import(name string) (*types.Package, error) {
   430  	pkg := w.imported[name]
   431  	if pkg != nil {
   432  		if pkg == &importing {
   433  			log.Fatalf("cycle importing package %q", name)
   434  		}
   435  		return pkg, nil
   436  	}
   437  	w.imported[name] = &importing
   438  
   439  	root := w.root
   440  	if strings.HasPrefix(name, "golang_org/x/") {
   441  		root = filepath.Join(root, "vendor")
   442  	}
   443  
   444  	// Determine package files.
   445  	dir := filepath.Join(root, filepath.FromSlash(name))
   446  	if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
   447  		log.Fatalf("no source in tree for import %q: %v", name, err)
   448  	}
   449  
   450  	context := w.context
   451  	if context == nil {
   452  		context = &build.Default
   453  	}
   454  
   455  	// Look in cache.
   456  	// If we've already done an import with the same set
   457  	// of relevant tags, reuse the result.
   458  	var key string
   459  	if usePkgCache {
   460  		if tags, ok := pkgTags[dir]; ok {
   461  			key = tagKey(dir, context, tags)
   462  			if pkg := pkgCache[key]; pkg != nil {
   463  				w.imported[name] = pkg
   464  				return pkg, nil
   465  			}
   466  		}
   467  	}
   468  
   469  	info, err := context.ImportDir(dir, 0)
   470  	if err != nil {
   471  		if _, nogo := err.(*build.NoGoError); nogo {
   472  			return nil, nil
   473  		}
   474  		log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
   475  	}
   476  
   477  	// Save tags list first time we see a directory.
   478  	if usePkgCache {
   479  		if _, ok := pkgTags[dir]; !ok {
   480  			pkgTags[dir] = info.AllTags
   481  			key = tagKey(dir, context, info.AllTags)
   482  		}
   483  	}
   484  
   485  	filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
   486  
   487  	// Parse package files.
   488  	var files []*ast.File
   489  	for _, file := range filenames {
   490  		f, err := w.parseFile(dir, file)
   491  		if err != nil {
   492  			log.Fatalf("error parsing package %s: %s", name, err)
   493  		}
   494  		files = append(files, f)
   495  	}
   496  
   497  	// Type-check package files.
   498  	conf := types.Config{
   499  		IgnoreFuncBodies: true,
   500  		FakeImportC:      true,
   501  		Importer:         w,
   502  	}
   503  	pkg, err = conf.Check(name, fset, files, nil)
   504  	if err != nil {
   505  		ctxt := "<no context>"
   506  		if w.context != nil {
   507  			ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
   508  		}
   509  		log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
   510  	}
   511  
   512  	if usePkgCache {
   513  		pkgCache[key] = pkg
   514  	}
   515  
   516  	w.imported[name] = pkg
   517  	return pkg, nil
   518  }
   519  
   520  // pushScope enters a new scope (walking a package, type, node, etc)
   521  // and returns a function that will leave the scope (with sanity checking
   522  // for mismatched pushes & pops)
   523  func (w *Walker) pushScope(name string) (popFunc func()) {
   524  	w.scope = append(w.scope, name)
   525  	return func() {
   526  		if len(w.scope) == 0 {
   527  			log.Fatalf("attempt to leave scope %q with empty scope list", name)
   528  		}
   529  		if w.scope[len(w.scope)-1] != name {
   530  			log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
   531  		}
   532  		w.scope = w.scope[:len(w.scope)-1]
   533  	}
   534  }
   535  
   536  func sortedMethodNames(typ *types.Interface) []string {
   537  	n := typ.NumMethods()
   538  	list := make([]string, n)
   539  	for i := range list {
   540  		list[i] = typ.Method(i).Name()
   541  	}
   542  	sort.Strings(list)
   543  	return list
   544  }
   545  
   546  func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
   547  	switch typ := typ.(type) {
   548  	case *types.Basic:
   549  		s := typ.Name()
   550  		switch typ.Kind() {
   551  		case types.UnsafePointer:
   552  			s = "unsafe.Pointer"
   553  		case types.UntypedBool:
   554  			s = "ideal-bool"
   555  		case types.UntypedInt:
   556  			s = "ideal-int"
   557  		case types.UntypedRune:
   558  			// "ideal-char" for compatibility with old tool
   559  			// TODO(gri) change to "ideal-rune"
   560  			s = "ideal-char"
   561  		case types.UntypedFloat:
   562  			s = "ideal-float"
   563  		case types.UntypedComplex:
   564  			s = "ideal-complex"
   565  		case types.UntypedString:
   566  			s = "ideal-string"
   567  		case types.UntypedNil:
   568  			panic("should never see untyped nil type")
   569  		default:
   570  			switch s {
   571  			case "byte":
   572  				s = "uint8"
   573  			case "rune":
   574  				s = "int32"
   575  			}
   576  		}
   577  		buf.WriteString(s)
   578  
   579  	case *types.Array:
   580  		fmt.Fprintf(buf, "[%d]", typ.Len())
   581  		w.writeType(buf, typ.Elem())
   582  
   583  	case *types.Slice:
   584  		buf.WriteString("[]")
   585  		w.writeType(buf, typ.Elem())
   586  
   587  	case *types.Struct:
   588  		buf.WriteString("struct")
   589  
   590  	case *types.Pointer:
   591  		buf.WriteByte('*')
   592  		w.writeType(buf, typ.Elem())
   593  
   594  	case *types.Tuple:
   595  		panic("should never see a tuple type")
   596  
   597  	case *types.Signature:
   598  		buf.WriteString("func")
   599  		w.writeSignature(buf, typ)
   600  
   601  	case *types.Interface:
   602  		buf.WriteString("interface{")
   603  		if typ.NumMethods() > 0 {
   604  			buf.WriteByte(' ')
   605  			buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
   606  			buf.WriteByte(' ')
   607  		}
   608  		buf.WriteString("}")
   609  
   610  	case *types.Map:
   611  		buf.WriteString("map[")
   612  		w.writeType(buf, typ.Key())
   613  		buf.WriteByte(']')
   614  		w.writeType(buf, typ.Elem())
   615  
   616  	case *types.Chan:
   617  		var s string
   618  		switch typ.Dir() {
   619  		case types.SendOnly:
   620  			s = "chan<- "
   621  		case types.RecvOnly:
   622  			s = "<-chan "
   623  		case types.SendRecv:
   624  			s = "chan "
   625  		default:
   626  			panic("unreachable")
   627  		}
   628  		buf.WriteString(s)
   629  		w.writeType(buf, typ.Elem())
   630  
   631  	case *types.Named:
   632  		obj := typ.Obj()
   633  		pkg := obj.Pkg()
   634  		if pkg != nil && pkg != w.current {
   635  			buf.WriteString(pkg.Name())
   636  			buf.WriteByte('.')
   637  		}
   638  		buf.WriteString(typ.Obj().Name())
   639  
   640  	default:
   641  		panic(fmt.Sprintf("unknown type %T", typ))
   642  	}
   643  }
   644  
   645  func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
   646  	w.writeParams(buf, sig.Params(), sig.Variadic())
   647  	switch res := sig.Results(); res.Len() {
   648  	case 0:
   649  		// nothing to do
   650  	case 1:
   651  		buf.WriteByte(' ')
   652  		w.writeType(buf, res.At(0).Type())
   653  	default:
   654  		buf.WriteByte(' ')
   655  		w.writeParams(buf, res, false)
   656  	}
   657  }
   658  
   659  func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
   660  	buf.WriteByte('(')
   661  	for i, n := 0, t.Len(); i < n; i++ {
   662  		if i > 0 {
   663  			buf.WriteString(", ")
   664  		}
   665  		typ := t.At(i).Type()
   666  		if variadic && i+1 == n {
   667  			buf.WriteString("...")
   668  			typ = typ.(*types.Slice).Elem()
   669  		}
   670  		w.writeType(buf, typ)
   671  	}
   672  	buf.WriteByte(')')
   673  }
   674  
   675  func (w *Walker) typeString(typ types.Type) string {
   676  	var buf bytes.Buffer
   677  	w.writeType(&buf, typ)
   678  	return buf.String()
   679  }
   680  
   681  func (w *Walker) signatureString(sig *types.Signature) string {
   682  	var buf bytes.Buffer
   683  	w.writeSignature(&buf, sig)
   684  	return buf.String()
   685  }
   686  
   687  func (w *Walker) emitObj(obj types.Object) {
   688  	switch obj := obj.(type) {
   689  	case *types.Const:
   690  		w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
   691  		x := obj.Val()
   692  		short := x.String()
   693  		exact := x.ExactString()
   694  		if short == exact {
   695  			w.emitf("const %s = %s", obj.Name(), short)
   696  		} else {
   697  			w.emitf("const %s = %s  // %s", obj.Name(), short, exact)
   698  		}
   699  	case *types.Var:
   700  		w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
   701  	case *types.TypeName:
   702  		w.emitType(obj)
   703  	case *types.Func:
   704  		w.emitFunc(obj)
   705  	default:
   706  		panic("unknown object: " + obj.String())
   707  	}
   708  }
   709  
   710  func (w *Walker) emitType(obj *types.TypeName) {
   711  	name := obj.Name()
   712  	typ := obj.Type()
   713  	switch typ := typ.Underlying().(type) {
   714  	case *types.Struct:
   715  		w.emitStructType(name, typ)
   716  	case *types.Interface:
   717  		w.emitIfaceType(name, typ)
   718  		return // methods are handled by emitIfaceType
   719  	default:
   720  		w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
   721  	}
   722  
   723  	// emit methods with value receiver
   724  	var methodNames map[string]bool
   725  	vset := types.NewMethodSet(typ)
   726  	for i, n := 0, vset.Len(); i < n; i++ {
   727  		m := vset.At(i)
   728  		if m.Obj().Exported() {
   729  			w.emitMethod(m)
   730  			if methodNames == nil {
   731  				methodNames = make(map[string]bool)
   732  			}
   733  			methodNames[m.Obj().Name()] = true
   734  		}
   735  	}
   736  
   737  	// emit methods with pointer receiver; exclude
   738  	// methods that we have emitted already
   739  	// (the method set of *T includes the methods of T)
   740  	pset := types.NewMethodSet(types.NewPointer(typ))
   741  	for i, n := 0, pset.Len(); i < n; i++ {
   742  		m := pset.At(i)
   743  		if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
   744  			w.emitMethod(m)
   745  		}
   746  	}
   747  }
   748  
   749  func (w *Walker) emitStructType(name string, typ *types.Struct) {
   750  	typeStruct := fmt.Sprintf("type %s struct", name)
   751  	w.emitf(typeStruct)
   752  	defer w.pushScope(typeStruct)()
   753  
   754  	for i := 0; i < typ.NumFields(); i++ {
   755  		f := typ.Field(i)
   756  		if !f.Exported() {
   757  			continue
   758  		}
   759  		typ := f.Type()
   760  		if f.Anonymous() {
   761  			w.emitf("embedded %s", w.typeString(typ))
   762  			continue
   763  		}
   764  		w.emitf("%s %s", f.Name(), w.typeString(typ))
   765  	}
   766  }
   767  
   768  func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
   769  	pop := w.pushScope("type " + name + " interface")
   770  
   771  	var methodNames []string
   772  	complete := true
   773  	mset := types.NewMethodSet(typ)
   774  	for i, n := 0, mset.Len(); i < n; i++ {
   775  		m := mset.At(i).Obj().(*types.Func)
   776  		if !m.Exported() {
   777  			complete = false
   778  			continue
   779  		}
   780  		methodNames = append(methodNames, m.Name())
   781  		w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
   782  	}
   783  
   784  	if !complete {
   785  		// The method set has unexported methods, so all the
   786  		// implementations are provided by the same package,
   787  		// so the method set can be extended. Instead of recording
   788  		// the full set of names (below), record only that there were
   789  		// unexported methods. (If the interface shrinks, we will notice
   790  		// because a method signature emitted during the last loop
   791  		// will disappear.)
   792  		w.emitf("unexported methods")
   793  	}
   794  
   795  	pop()
   796  
   797  	if !complete {
   798  		return
   799  	}
   800  
   801  	if len(methodNames) == 0 {
   802  		w.emitf("type %s interface {}", name)
   803  		return
   804  	}
   805  
   806  	sort.Strings(methodNames)
   807  	w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
   808  }
   809  
   810  func (w *Walker) emitFunc(f *types.Func) {
   811  	sig := f.Type().(*types.Signature)
   812  	if sig.Recv() != nil {
   813  		panic("method considered a regular function: " + f.String())
   814  	}
   815  	w.emitf("func %s%s", f.Name(), w.signatureString(sig))
   816  }
   817  
   818  func (w *Walker) emitMethod(m *types.Selection) {
   819  	sig := m.Type().(*types.Signature)
   820  	recv := sig.Recv().Type()
   821  	// report exported methods with unexported receiver base type
   822  	if true {
   823  		base := recv
   824  		if p, _ := recv.(*types.Pointer); p != nil {
   825  			base = p.Elem()
   826  		}
   827  		if obj := base.(*types.Named).Obj(); !obj.Exported() {
   828  			log.Fatalf("exported method with unexported receiver base type: %s", m)
   829  		}
   830  	}
   831  	w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig))
   832  }
   833  
   834  func (w *Walker) emitf(format string, args ...interface{}) {
   835  	f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
   836  	if strings.Contains(f, "\n") {
   837  		panic("feature contains newlines: " + f)
   838  	}
   839  
   840  	if _, dup := w.features[f]; dup {
   841  		panic("duplicate feature inserted: " + f)
   842  	}
   843  	w.features[f] = true
   844  
   845  	if *verbose {
   846  		log.Printf("feature: %s", f)
   847  	}
   848  }
   849  

View as plain text