...

Source file src/pkg/cmd/api/goapi.go

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

View as plain text