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