Source file src/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  	exec "internal/execabs"
    20  	"io"
    21  	"log"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"runtime"
    26  	"sort"
    27  	"strings"
    28  	"sync"
    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: "amd64", CgoEnabled: true},
    63  	{GOOS: "darwin", GOARCH: "amd64"},
    64  	{GOOS: "windows", GOARCH: "amd64"},
    65  	{GOOS: "windows", GOARCH: "386"},
    66  	{GOOS: "freebsd", GOARCH: "386", CgoEnabled: true},
    67  	{GOOS: "freebsd", GOARCH: "386"},
    68  	{GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true},
    69  	{GOOS: "freebsd", GOARCH: "amd64"},
    70  	{GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true},
    71  	{GOOS: "freebsd", GOARCH: "arm"},
    72  	{GOOS: "netbsd", GOARCH: "386", CgoEnabled: true},
    73  	{GOOS: "netbsd", GOARCH: "386"},
    74  	{GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true},
    75  	{GOOS: "netbsd", GOARCH: "amd64"},
    76  	{GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true},
    77  	{GOOS: "netbsd", GOARCH: "arm"},
    78  	{GOOS: "netbsd", GOARCH: "arm64", CgoEnabled: true},
    79  	{GOOS: "netbsd", GOARCH: "arm64"},
    80  	{GOOS: "openbsd", GOARCH: "386", CgoEnabled: true},
    81  	{GOOS: "openbsd", GOARCH: "386"},
    82  	{GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true},
    83  	{GOOS: "openbsd", GOARCH: "amd64"},
    84  }
    85  
    86  func contextName(c *build.Context) string {
    87  	s := c.GOOS + "-" + c.GOARCH
    88  	if c.CgoEnabled {
    89  		s += "-cgo"
    90  	}
    91  	if c.Dir != "" {
    92  		s += fmt.Sprintf(" [%s]", c.Dir)
    93  	}
    94  	return s
    95  }
    96  
    97  func parseContext(c string) *build.Context {
    98  	parts := strings.Split(c, "-")
    99  	if len(parts) < 2 {
   100  		log.Fatalf("bad context: %q", c)
   101  	}
   102  	bc := &build.Context{
   103  		GOOS:   parts[0],
   104  		GOARCH: parts[1],
   105  	}
   106  	if len(parts) == 3 {
   107  		if parts[2] == "cgo" {
   108  			bc.CgoEnabled = true
   109  		} else {
   110  			log.Fatalf("bad context: %q", c)
   111  		}
   112  	}
   113  	return bc
   114  }
   115  
   116  func setContexts() {
   117  	contexts = []*build.Context{}
   118  	for _, c := range strings.Split(*forceCtx, ",") {
   119  		contexts = append(contexts, parseContext(c))
   120  	}
   121  }
   122  
   123  var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
   124  
   125  func main() {
   126  	flag.Parse()
   127  
   128  	if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
   129  		if *nextFile != "" {
   130  			fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
   131  			*nextFile = ""
   132  		}
   133  	}
   134  
   135  	if *forceCtx != "" {
   136  		setContexts()
   137  	}
   138  	for _, c := range contexts {
   139  		c.Compiler = build.Default.Compiler
   140  	}
   141  
   142  	walkers := make([]*Walker, len(contexts))
   143  	var wg sync.WaitGroup
   144  	for i, context := range contexts {
   145  		i, context := i, context
   146  		wg.Add(1)
   147  		go func() {
   148  			defer wg.Done()
   149  			walkers[i] = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
   150  		}()
   151  	}
   152  	wg.Wait()
   153  
   154  	var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
   155  	for _, w := range walkers {
   156  		pkgNames := w.stdPackages
   157  		if flag.NArg() > 0 {
   158  			pkgNames = flag.Args()
   159  		}
   160  
   161  		for _, name := range pkgNames {
   162  			pkg, err := w.Import(name)
   163  			if _, nogo := err.(*build.NoGoError); nogo {
   164  				continue
   165  			}
   166  			if err != nil {
   167  				log.Fatalf("Import(%q): %v", name, err)
   168  			}
   169  			w.export(pkg)
   170  		}
   171  
   172  		ctxName := contextName(w.context)
   173  		for _, f := range w.Features() {
   174  			if featureCtx[f] == nil {
   175  				featureCtx[f] = make(map[string]bool)
   176  			}
   177  			featureCtx[f][ctxName] = true
   178  		}
   179  	}
   180  
   181  	var features []string
   182  	for f, cmap := range featureCtx {
   183  		if len(cmap) == len(contexts) {
   184  			features = append(features, f)
   185  			continue
   186  		}
   187  		comma := strings.Index(f, ",")
   188  		for cname := range cmap {
   189  			f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
   190  			features = append(features, f2)
   191  		}
   192  	}
   193  
   194  	fail := false
   195  	defer func() {
   196  		if fail {
   197  			os.Exit(1)
   198  		}
   199  	}()
   200  
   201  	bw := bufio.NewWriter(os.Stdout)
   202  	defer bw.Flush()
   203  
   204  	if *checkFile == "" {
   205  		sort.Strings(features)
   206  		for _, f := range features {
   207  			fmt.Fprintln(bw, f)
   208  		}
   209  		return
   210  	}
   211  
   212  	var required []string
   213  	for _, file := range strings.Split(*checkFile, ",") {
   214  		required = append(required, fileFeatures(file)...)
   215  	}
   216  	optional := fileFeatures(*nextFile)
   217  	exception := fileFeatures(*exceptFile)
   218  	fail = !compareAPI(bw, features, required, optional, exception, *allowNew)
   219  }
   220  
   221  // export emits the exported package features.
   222  func (w *Walker) export(pkg *types.Package) {
   223  	if *verbose {
   224  		log.Println(pkg)
   225  	}
   226  	pop := w.pushScope("pkg " + pkg.Path())
   227  	w.current = pkg
   228  	scope := pkg.Scope()
   229  	for _, name := range scope.Names() {
   230  		if token.IsExported(name) {
   231  			w.emitObj(scope.Lookup(name))
   232  		}
   233  	}
   234  	pop()
   235  }
   236  
   237  func set(items []string) map[string]bool {
   238  	s := make(map[string]bool)
   239  	for _, v := range items {
   240  		s[v] = true
   241  	}
   242  	return s
   243  }
   244  
   245  var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
   246  
   247  func featureWithoutContext(f string) string {
   248  	if !strings.Contains(f, "(") {
   249  		return f
   250  	}
   251  	return spaceParensRx.ReplaceAllString(f, "")
   252  }
   253  
   254  // portRemoved reports whether the given port-specific API feature is
   255  // okay to no longer exist because its port was removed.
   256  func portRemoved(feature string) bool {
   257  	return strings.Contains(feature, "(darwin-386)") ||
   258  		strings.Contains(feature, "(darwin-386-cgo)")
   259  }
   260  
   261  func compareAPI(w io.Writer, features, required, optional, exception []string, allowAdd bool) (ok bool) {
   262  	ok = true
   263  
   264  	optionalSet := set(optional)
   265  	exceptionSet := set(exception)
   266  	featureSet := set(features)
   267  
   268  	sort.Strings(features)
   269  	sort.Strings(required)
   270  
   271  	take := func(sl *[]string) string {
   272  		s := (*sl)[0]
   273  		*sl = (*sl)[1:]
   274  		return s
   275  	}
   276  
   277  	for len(required) > 0 || len(features) > 0 {
   278  		switch {
   279  		case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
   280  			feature := take(&required)
   281  			if exceptionSet[feature] {
   282  				// An "unfortunate" case: the feature was once
   283  				// included in the API (e.g. go1.txt), but was
   284  				// subsequently removed. These are already
   285  				// acknowledged by being in the file
   286  				// "api/except.txt". No need to print them out
   287  				// here.
   288  			} else if portRemoved(feature) {
   289  				// okay.
   290  			} else if featureSet[featureWithoutContext(feature)] {
   291  				// okay.
   292  			} else {
   293  				fmt.Fprintf(w, "-%s\n", feature)
   294  				ok = false // broke compatibility
   295  			}
   296  		case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
   297  			newFeature := take(&features)
   298  			if optionalSet[newFeature] {
   299  				// Known added feature to the upcoming release.
   300  				// Delete it from the map so we can detect any upcoming features
   301  				// which were never seen.  (so we can clean up the nextFile)
   302  				delete(optionalSet, newFeature)
   303  			} else {
   304  				fmt.Fprintf(w, "+%s\n", newFeature)
   305  				if !allowAdd {
   306  					ok = false // we're in lock-down mode for next release
   307  				}
   308  			}
   309  		default:
   310  			take(&required)
   311  			take(&features)
   312  		}
   313  	}
   314  
   315  	// In next file, but not in API.
   316  	var missing []string
   317  	for feature := range optionalSet {
   318  		missing = append(missing, feature)
   319  	}
   320  	sort.Strings(missing)
   321  	for _, feature := range missing {
   322  		fmt.Fprintf(w, "±%s\n", feature)
   323  	}
   324  	return
   325  }
   326  
   327  // aliasReplacer applies type aliases to earlier API files,
   328  // to avoid misleading negative results.
   329  // This makes all the references to os.FileInfo in go1.txt
   330  // be read as if they said fs.FileInfo, since os.FileInfo is now an alias.
   331  // If there are many of these, we could do a more general solution,
   332  // but for now the replacer is fine.
   333  var aliasReplacer = strings.NewReplacer(
   334  	"os.FileInfo", "fs.FileInfo",
   335  	"os.FileMode", "fs.FileMode",
   336  	"os.PathError", "fs.PathError",
   337  )
   338  
   339  func fileFeatures(filename string) []string {
   340  	if filename == "" {
   341  		return nil
   342  	}
   343  	bs, err := os.ReadFile(filename)
   344  	if err != nil {
   345  		log.Fatalf("Error reading file %s: %v", filename, err)
   346  	}
   347  	s := string(bs)
   348  	s = aliasReplacer.Replace(s)
   349  	lines := strings.Split(s, "\n")
   350  	var nonblank []string
   351  	for _, line := range lines {
   352  		line = strings.TrimSpace(line)
   353  		if line != "" && !strings.HasPrefix(line, "#") {
   354  			nonblank = append(nonblank, line)
   355  		}
   356  	}
   357  	return nonblank
   358  }
   359  
   360  var fset = token.NewFileSet()
   361  
   362  type Walker struct {
   363  	context     *build.Context
   364  	root        string
   365  	scope       []string
   366  	current     *types.Package
   367  	features    map[string]bool              // set
   368  	imported    map[string]*types.Package    // packages already imported
   369  	stdPackages []string                     // names, omitting "unsafe", internal, and vendored packages
   370  	importMap   map[string]map[string]string // importer dir -> import path -> canonical path
   371  	importDir   map[string]string            // canonical import path -> dir
   372  
   373  }
   374  
   375  func NewWalker(context *build.Context, root string) *Walker {
   376  	w := &Walker{
   377  		context:  context,
   378  		root:     root,
   379  		features: map[string]bool{},
   380  		imported: map[string]*types.Package{"unsafe": types.Unsafe},
   381  	}
   382  	w.loadImports()
   383  	return w
   384  }
   385  
   386  func (w *Walker) Features() (fs []string) {
   387  	for f := range w.features {
   388  		fs = append(fs, f)
   389  	}
   390  	sort.Strings(fs)
   391  	return
   392  }
   393  
   394  var parsedFileCache = make(map[string]*ast.File)
   395  
   396  func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
   397  	filename := filepath.Join(dir, file)
   398  	if f := parsedFileCache[filename]; f != nil {
   399  		return f, nil
   400  	}
   401  
   402  	f, err := parser.ParseFile(fset, filename, nil, 0)
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	parsedFileCache[filename] = f
   407  
   408  	return f, nil
   409  }
   410  
   411  // Disable before debugging non-obvious errors from the type-checker.
   412  const usePkgCache = true
   413  
   414  var (
   415  	pkgCache = map[string]*types.Package{} // map tagKey to package
   416  	pkgTags  = map[string][]string{}       // map import dir to list of relevant tags
   417  )
   418  
   419  // tagKey returns the tag-based key to use in the pkgCache.
   420  // It is a comma-separated string; the first part is dir, the rest tags.
   421  // The satisfied tags are derived from context but only those that
   422  // matter (the ones listed in the tags argument plus GOOS and GOARCH) are used.
   423  // The tags list, which came from go/build's Package.AllTags,
   424  // is known to be sorted.
   425  func tagKey(dir string, context *build.Context, tags []string) string {
   426  	ctags := map[string]bool{
   427  		context.GOOS:   true,
   428  		context.GOARCH: true,
   429  	}
   430  	if context.CgoEnabled {
   431  		ctags["cgo"] = true
   432  	}
   433  	for _, tag := range context.BuildTags {
   434  		ctags[tag] = true
   435  	}
   436  	// TODO: ReleaseTags (need to load default)
   437  	key := dir
   438  
   439  	// explicit on GOOS and GOARCH as global cache will use "all" cached packages for
   440  	// an indirect imported package. See https://github.com/golang/go/issues/21181
   441  	// for more detail.
   442  	tags = append(tags, context.GOOS, context.GOARCH)
   443  	sort.Strings(tags)
   444  
   445  	for _, tag := range tags {
   446  		if ctags[tag] {
   447  			key += "," + tag
   448  			ctags[tag] = false
   449  		}
   450  	}
   451  	return key
   452  }
   453  
   454  type listImports struct {
   455  	stdPackages []string                     // names, omitting "unsafe", internal, and vendored packages
   456  	importDir   map[string]string            // canonical import path → directory
   457  	importMap   map[string]map[string]string // import path → canonical import path
   458  }
   459  
   460  var listCache sync.Map // map[string]listImports, keyed by contextName
   461  
   462  // listSem is a semaphore restricting concurrent invocations of 'go list'. 'go
   463  // list' has its own internal concurrency, so we use a hard-coded constant (to
   464  // allow the I/O-intensive phases of 'go list' to overlap) instead of scaling
   465  // all the way up to GOMAXPROCS.
   466  var listSem = make(chan semToken, 2)
   467  
   468  type semToken struct{}
   469  
   470  // loadImports populates w with information about the packages in the standard
   471  // library and the packages they themselves import in w's build context.
   472  //
   473  // The source import path and expanded import path are identical except for vendored packages.
   474  // For example, on return:
   475  //
   476  //	w.importMap["math"] = "math"
   477  //	w.importDir["math"] = "<goroot>/src/math"
   478  //
   479  //	w.importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route"
   480  //	w.importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route"
   481  //
   482  // Since the set of packages that exist depends on context, the result of
   483  // loadImports also depends on context. However, to improve test running time
   484  // the configuration for each environment is cached across runs.
   485  func (w *Walker) loadImports() {
   486  	if w.context == nil {
   487  		return // test-only Walker; does not use the import map
   488  	}
   489  
   490  	name := contextName(w.context)
   491  
   492  	imports, ok := listCache.Load(name)
   493  	if !ok {
   494  		listSem <- semToken{}
   495  		defer func() { <-listSem }()
   496  
   497  		cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
   498  		cmd.Env = listEnv(w.context)
   499  		if w.context.Dir != "" {
   500  			cmd.Dir = w.context.Dir
   501  		}
   502  		out, err := cmd.CombinedOutput()
   503  		if err != nil {
   504  			log.Fatalf("loading imports: %v\n%s", err, out)
   505  		}
   506  
   507  		var stdPackages []string
   508  		importMap := make(map[string]map[string]string)
   509  		importDir := make(map[string]string)
   510  		dec := json.NewDecoder(bytes.NewReader(out))
   511  		for {
   512  			var pkg struct {
   513  				ImportPath, Dir string
   514  				ImportMap       map[string]string
   515  				Standard        bool
   516  			}
   517  			err := dec.Decode(&pkg)
   518  			if err == io.EOF {
   519  				break
   520  			}
   521  			if err != nil {
   522  				log.Fatalf("go list: invalid output: %v", err)
   523  			}
   524  
   525  			// - Package "unsafe" contains special signatures requiring
   526  			//   extra care when printing them - ignore since it is not
   527  			//   going to change w/o a language change.
   528  			// - Internal and vendored packages do not contribute to our
   529  			//   API surface. (If we are running within the "std" module,
   530  			//   vendored dependencies appear as themselves instead of
   531  			//   their "vendor/" standard-library copies.)
   532  			// - 'go list std' does not include commands, which cannot be
   533  			//   imported anyway.
   534  			if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
   535  				stdPackages = append(stdPackages, ip)
   536  			}
   537  			importDir[pkg.ImportPath] = pkg.Dir
   538  			if len(pkg.ImportMap) > 0 {
   539  				importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
   540  			}
   541  			for k, v := range pkg.ImportMap {
   542  				importMap[pkg.Dir][k] = v
   543  			}
   544  		}
   545  
   546  		sort.Strings(stdPackages)
   547  		imports = listImports{
   548  			stdPackages: stdPackages,
   549  			importMap:   importMap,
   550  			importDir:   importDir,
   551  		}
   552  		imports, _ = listCache.LoadOrStore(name, imports)
   553  	}
   554  
   555  	li := imports.(listImports)
   556  	w.stdPackages = li.stdPackages
   557  	w.importDir = li.importDir
   558  	w.importMap = li.importMap
   559  }
   560  
   561  // listEnv returns the process environment to use when invoking 'go list' for
   562  // the given context.
   563  func listEnv(c *build.Context) []string {
   564  	if c == nil {
   565  		return os.Environ()
   566  	}
   567  
   568  	environ := append(os.Environ(),
   569  		"GOOS="+c.GOOS,
   570  		"GOARCH="+c.GOARCH)
   571  	if c.CgoEnabled {
   572  		environ = append(environ, "CGO_ENABLED=1")
   573  	} else {
   574  		environ = append(environ, "CGO_ENABLED=0")
   575  	}
   576  	return environ
   577  }
   578  
   579  // Importing is a sentinel taking the place in Walker.imported
   580  // for a package that is in the process of being imported.
   581  var importing types.Package
   582  
   583  func (w *Walker) Import(name string) (*types.Package, error) {
   584  	return w.ImportFrom(name, "", 0)
   585  }
   586  
   587  func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
   588  	name := fromPath
   589  	if canonical, ok := w.importMap[fromDir][fromPath]; ok {
   590  		name = canonical
   591  	}
   592  
   593  	pkg := w.imported[name]
   594  	if pkg != nil {
   595  		if pkg == &importing {
   596  			log.Fatalf("cycle importing package %q", name)
   597  		}
   598  		return pkg, nil
   599  	}
   600  	w.imported[name] = &importing
   601  
   602  	// Determine package files.
   603  	dir := w.importDir[name]
   604  	if dir == "" {
   605  		dir = filepath.Join(w.root, filepath.FromSlash(name))
   606  	}
   607  	if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
   608  		log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
   609  	}
   610  
   611  	context := w.context
   612  	if context == nil {
   613  		context = &build.Default
   614  	}
   615  
   616  	// Look in cache.
   617  	// If we've already done an import with the same set
   618  	// of relevant tags, reuse the result.
   619  	var key string
   620  	if usePkgCache {
   621  		if tags, ok := pkgTags[dir]; ok {
   622  			key = tagKey(dir, context, tags)
   623  			if pkg := pkgCache[key]; pkg != nil {
   624  				w.imported[name] = pkg
   625  				return pkg, nil
   626  			}
   627  		}
   628  	}
   629  
   630  	info, err := context.ImportDir(dir, 0)
   631  	if err != nil {
   632  		if _, nogo := err.(*build.NoGoError); nogo {
   633  			return nil, err
   634  		}
   635  		log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
   636  	}
   637  
   638  	// Save tags list first time we see a directory.
   639  	if usePkgCache {
   640  		if _, ok := pkgTags[dir]; !ok {
   641  			pkgTags[dir] = info.AllTags
   642  			key = tagKey(dir, context, info.AllTags)
   643  		}
   644  	}
   645  
   646  	filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
   647  
   648  	// Parse package files.
   649  	var files []*ast.File
   650  	for _, file := range filenames {
   651  		f, err := w.parseFile(dir, file)
   652  		if err != nil {
   653  			log.Fatalf("error parsing package %s: %s", name, err)
   654  		}
   655  		files = append(files, f)
   656  	}
   657  
   658  	// Type-check package files.
   659  	var sizes types.Sizes
   660  	if w.context != nil {
   661  		sizes = types.SizesFor(w.context.Compiler, w.context.GOARCH)
   662  	}
   663  	conf := types.Config{
   664  		IgnoreFuncBodies: true,
   665  		FakeImportC:      true,
   666  		Importer:         w,
   667  		Sizes:            sizes,
   668  	}
   669  	pkg, err = conf.Check(name, fset, files, nil)
   670  	if err != nil {
   671  		ctxt := "<no context>"
   672  		if w.context != nil {
   673  			ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
   674  		}
   675  		log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
   676  	}
   677  
   678  	if usePkgCache {
   679  		pkgCache[key] = pkg
   680  	}
   681  
   682  	w.imported[name] = pkg
   683  	return pkg, nil
   684  }
   685  
   686  // pushScope enters a new scope (walking a package, type, node, etc)
   687  // and returns a function that will leave the scope (with sanity checking
   688  // for mismatched pushes & pops)
   689  func (w *Walker) pushScope(name string) (popFunc func()) {
   690  	w.scope = append(w.scope, name)
   691  	return func() {
   692  		if len(w.scope) == 0 {
   693  			log.Fatalf("attempt to leave scope %q with empty scope list", name)
   694  		}
   695  		if w.scope[len(w.scope)-1] != name {
   696  			log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
   697  		}
   698  		w.scope = w.scope[:len(w.scope)-1]
   699  	}
   700  }
   701  
   702  func sortedMethodNames(typ *types.Interface) []string {
   703  	n := typ.NumMethods()
   704  	list := make([]string, n)
   705  	for i := range list {
   706  		list[i] = typ.Method(i).Name()
   707  	}
   708  	sort.Strings(list)
   709  	return list
   710  }
   711  
   712  // sortedEmbeddeds returns constraint types embedded in an
   713  // interface. It does not include embedded interface types or methods.
   714  func (w *Walker) sortedEmbeddeds(typ *types.Interface) []string {
   715  	n := typ.NumEmbeddeds()
   716  	list := make([]string, 0, n)
   717  	for i := 0; i < n; i++ {
   718  		emb := typ.EmbeddedType(i)
   719  		switch emb := emb.(type) {
   720  		case *types.Interface:
   721  			list = append(list, w.sortedEmbeddeds(emb)...)
   722  		case *types.Union:
   723  			var buf bytes.Buffer
   724  			nu := emb.Len()
   725  			for i := 0; i < nu; i++ {
   726  				if i > 0 {
   727  					buf.WriteString(" | ")
   728  				}
   729  				term := emb.Term(i)
   730  				if term.Tilde() {
   731  					buf.WriteByte('~')
   732  				}
   733  				w.writeType(&buf, term.Type())
   734  			}
   735  			list = append(list, buf.String())
   736  		}
   737  	}
   738  	sort.Strings(list)
   739  	return list
   740  }
   741  
   742  func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
   743  	switch typ := typ.(type) {
   744  	case *types.Basic:
   745  		s := typ.Name()
   746  		switch typ.Kind() {
   747  		case types.UnsafePointer:
   748  			s = "unsafe.Pointer"
   749  		case types.UntypedBool:
   750  			s = "ideal-bool"
   751  		case types.UntypedInt:
   752  			s = "ideal-int"
   753  		case types.UntypedRune:
   754  			// "ideal-char" for compatibility with old tool
   755  			// TODO(gri) change to "ideal-rune"
   756  			s = "ideal-char"
   757  		case types.UntypedFloat:
   758  			s = "ideal-float"
   759  		case types.UntypedComplex:
   760  			s = "ideal-complex"
   761  		case types.UntypedString:
   762  			s = "ideal-string"
   763  		case types.UntypedNil:
   764  			panic("should never see untyped nil type")
   765  		default:
   766  			switch s {
   767  			case "byte":
   768  				s = "uint8"
   769  			case "rune":
   770  				s = "int32"
   771  			}
   772  		}
   773  		buf.WriteString(s)
   774  
   775  	case *types.Array:
   776  		fmt.Fprintf(buf, "[%d]", typ.Len())
   777  		w.writeType(buf, typ.Elem())
   778  
   779  	case *types.Slice:
   780  		buf.WriteString("[]")
   781  		w.writeType(buf, typ.Elem())
   782  
   783  	case *types.Struct:
   784  		buf.WriteString("struct")
   785  
   786  	case *types.Pointer:
   787  		buf.WriteByte('*')
   788  		w.writeType(buf, typ.Elem())
   789  
   790  	case *types.Tuple:
   791  		panic("should never see a tuple type")
   792  
   793  	case *types.Signature:
   794  		buf.WriteString("func")
   795  		w.writeSignature(buf, typ)
   796  
   797  	case *types.Interface:
   798  		buf.WriteString("interface{")
   799  		if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
   800  			buf.WriteByte(' ')
   801  		}
   802  		if typ.NumMethods() > 0 {
   803  			buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
   804  		}
   805  		if typ.NumEmbeddeds() > 0 {
   806  			buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", "))
   807  		}
   808  		if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
   809  			buf.WriteByte(' ')
   810  		}
   811  		buf.WriteString("}")
   812  
   813  	case *types.Map:
   814  		buf.WriteString("map[")
   815  		w.writeType(buf, typ.Key())
   816  		buf.WriteByte(']')
   817  		w.writeType(buf, typ.Elem())
   818  
   819  	case *types.Chan:
   820  		var s string
   821  		switch typ.Dir() {
   822  		case types.SendOnly:
   823  			s = "chan<- "
   824  		case types.RecvOnly:
   825  			s = "<-chan "
   826  		case types.SendRecv:
   827  			s = "chan "
   828  		default:
   829  			panic("unreachable")
   830  		}
   831  		buf.WriteString(s)
   832  		w.writeType(buf, typ.Elem())
   833  
   834  	case *types.Named:
   835  		obj := typ.Obj()
   836  		pkg := obj.Pkg()
   837  		if pkg != nil && pkg != w.current {
   838  			buf.WriteString(pkg.Name())
   839  			buf.WriteByte('.')
   840  		}
   841  		buf.WriteString(typ.Obj().Name())
   842  
   843  	case *types.TypeParam:
   844  		// Type parameter names may change, so use a placeholder instead.
   845  		fmt.Fprintf(buf, "$%d", typ.Index())
   846  
   847  	default:
   848  		panic(fmt.Sprintf("unknown type %T", typ))
   849  	}
   850  }
   851  
   852  func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
   853  	if tparams := sig.TypeParams(); tparams != nil {
   854  		w.writeTypeParams(buf, tparams, true)
   855  	}
   856  	w.writeParams(buf, sig.Params(), sig.Variadic())
   857  	switch res := sig.Results(); res.Len() {
   858  	case 0:
   859  		// nothing to do
   860  	case 1:
   861  		buf.WriteByte(' ')
   862  		w.writeType(buf, res.At(0).Type())
   863  	default:
   864  		buf.WriteByte(' ')
   865  		w.writeParams(buf, res, false)
   866  	}
   867  }
   868  
   869  func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
   870  	buf.WriteByte('[')
   871  	c := tparams.Len()
   872  	for i := 0; i < c; i++ {
   873  		if i > 0 {
   874  			buf.WriteString(", ")
   875  		}
   876  		tp := tparams.At(i)
   877  		w.writeType(buf, tp)
   878  		if withConstraints {
   879  			buf.WriteByte(' ')
   880  			w.writeType(buf, tp.Constraint())
   881  		}
   882  	}
   883  	buf.WriteByte(']')
   884  }
   885  
   886  func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
   887  	buf.WriteByte('(')
   888  	for i, n := 0, t.Len(); i < n; i++ {
   889  		if i > 0 {
   890  			buf.WriteString(", ")
   891  		}
   892  		typ := t.At(i).Type()
   893  		if variadic && i+1 == n {
   894  			buf.WriteString("...")
   895  			typ = typ.(*types.Slice).Elem()
   896  		}
   897  		w.writeType(buf, typ)
   898  	}
   899  	buf.WriteByte(')')
   900  }
   901  
   902  func (w *Walker) typeString(typ types.Type) string {
   903  	var buf bytes.Buffer
   904  	w.writeType(&buf, typ)
   905  	return buf.String()
   906  }
   907  
   908  func (w *Walker) signatureString(sig *types.Signature) string {
   909  	var buf bytes.Buffer
   910  	w.writeSignature(&buf, sig)
   911  	return buf.String()
   912  }
   913  
   914  func (w *Walker) emitObj(obj types.Object) {
   915  	switch obj := obj.(type) {
   916  	case *types.Const:
   917  		w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
   918  		x := obj.Val()
   919  		short := x.String()
   920  		exact := x.ExactString()
   921  		if short == exact {
   922  			w.emitf("const %s = %s", obj.Name(), short)
   923  		} else {
   924  			w.emitf("const %s = %s  // %s", obj.Name(), short, exact)
   925  		}
   926  	case *types.Var:
   927  		w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
   928  	case *types.TypeName:
   929  		w.emitType(obj)
   930  	case *types.Func:
   931  		w.emitFunc(obj)
   932  	default:
   933  		panic("unknown object: " + obj.String())
   934  	}
   935  }
   936  
   937  func (w *Walker) emitType(obj *types.TypeName) {
   938  	name := obj.Name()
   939  	if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
   940  		var buf bytes.Buffer
   941  		buf.WriteString(name)
   942  		w.writeTypeParams(&buf, tparams, true)
   943  		name = buf.String()
   944  	}
   945  	typ := obj.Type()
   946  	if obj.IsAlias() {
   947  		w.emitf("type %s = %s", name, w.typeString(typ))
   948  		return
   949  	}
   950  	switch typ := typ.Underlying().(type) {
   951  	case *types.Struct:
   952  		w.emitStructType(name, typ)
   953  	case *types.Interface:
   954  		w.emitIfaceType(name, typ)
   955  		return // methods are handled by emitIfaceType
   956  	default:
   957  		w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
   958  	}
   959  
   960  	// emit methods with value receiver
   961  	var methodNames map[string]bool
   962  	vset := types.NewMethodSet(typ)
   963  	for i, n := 0, vset.Len(); i < n; i++ {
   964  		m := vset.At(i)
   965  		if m.Obj().Exported() {
   966  			w.emitMethod(m)
   967  			if methodNames == nil {
   968  				methodNames = make(map[string]bool)
   969  			}
   970  			methodNames[m.Obj().Name()] = true
   971  		}
   972  	}
   973  
   974  	// emit methods with pointer receiver; exclude
   975  	// methods that we have emitted already
   976  	// (the method set of *T includes the methods of T)
   977  	pset := types.NewMethodSet(types.NewPointer(typ))
   978  	for i, n := 0, pset.Len(); i < n; i++ {
   979  		m := pset.At(i)
   980  		if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
   981  			w.emitMethod(m)
   982  		}
   983  	}
   984  }
   985  
   986  func (w *Walker) emitStructType(name string, typ *types.Struct) {
   987  	typeStruct := fmt.Sprintf("type %s struct", name)
   988  	w.emitf(typeStruct)
   989  	defer w.pushScope(typeStruct)()
   990  
   991  	for i := 0; i < typ.NumFields(); i++ {
   992  		f := typ.Field(i)
   993  		if !f.Exported() {
   994  			continue
   995  		}
   996  		typ := f.Type()
   997  		if f.Anonymous() {
   998  			w.emitf("embedded %s", w.typeString(typ))
   999  			continue
  1000  		}
  1001  		w.emitf("%s %s", f.Name(), w.typeString(typ))
  1002  	}
  1003  }
  1004  
  1005  func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
  1006  	pop := w.pushScope("type " + name + " interface")
  1007  
  1008  	var methodNames []string
  1009  	complete := true
  1010  	mset := types.NewMethodSet(typ)
  1011  	for i, n := 0, mset.Len(); i < n; i++ {
  1012  		m := mset.At(i).Obj().(*types.Func)
  1013  		if !m.Exported() {
  1014  			complete = false
  1015  			continue
  1016  		}
  1017  		methodNames = append(methodNames, m.Name())
  1018  		w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
  1019  	}
  1020  
  1021  	if !complete {
  1022  		// The method set has unexported methods, so all the
  1023  		// implementations are provided by the same package,
  1024  		// so the method set can be extended. Instead of recording
  1025  		// the full set of names (below), record only that there were
  1026  		// unexported methods. (If the interface shrinks, we will notice
  1027  		// because a method signature emitted during the last loop
  1028  		// will disappear.)
  1029  		w.emitf("unexported methods")
  1030  	}
  1031  
  1032  	pop()
  1033  
  1034  	if !complete {
  1035  		return
  1036  	}
  1037  
  1038  	if len(methodNames) == 0 {
  1039  		w.emitf("type %s interface {}", name)
  1040  		return
  1041  	}
  1042  
  1043  	sort.Strings(methodNames)
  1044  	w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
  1045  }
  1046  
  1047  func (w *Walker) emitFunc(f *types.Func) {
  1048  	sig := f.Type().(*types.Signature)
  1049  	if sig.Recv() != nil {
  1050  		panic("method considered a regular function: " + f.String())
  1051  	}
  1052  	w.emitf("func %s%s", f.Name(), w.signatureString(sig))
  1053  }
  1054  
  1055  func (w *Walker) emitMethod(m *types.Selection) {
  1056  	sig := m.Type().(*types.Signature)
  1057  	recv := sig.Recv().Type()
  1058  	// report exported methods with unexported receiver base type
  1059  	if true {
  1060  		base := recv
  1061  		if p, _ := recv.(*types.Pointer); p != nil {
  1062  			base = p.Elem()
  1063  		}
  1064  		if obj := base.(*types.Named).Obj(); !obj.Exported() {
  1065  			log.Fatalf("exported method with unexported receiver base type: %s", m)
  1066  		}
  1067  	}
  1068  	tps := ""
  1069  	if rtp := sig.RecvTypeParams(); rtp != nil {
  1070  		var buf bytes.Buffer
  1071  		w.writeTypeParams(&buf, rtp, false)
  1072  		tps = buf.String()
  1073  	}
  1074  	w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
  1075  }
  1076  
  1077  func (w *Walker) emitf(format string, args ...any) {
  1078  	f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
  1079  	if strings.Contains(f, "\n") {
  1080  		panic("feature contains newlines: " + f)
  1081  	}
  1082  
  1083  	if _, dup := w.features[f]; dup {
  1084  		panic("duplicate feature inserted: " + f)
  1085  	}
  1086  	w.features[f] = true
  1087  
  1088  	if *verbose {
  1089  		log.Printf("feature: %s", f)
  1090  	}
  1091  }
  1092  

View as plain text