summaryrefslogtreecommitdiffstats
path: root/src/cmd/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
commit47ab3d4a42e9ab51c465c4322d2ec233f6324e6b (patch)
treea61a0ffd83f4a3def4b36e5c8e99630c559aa723 /src/cmd/api
parentInitial commit. (diff)
downloadgolang-1.18-47ab3d4a42e9ab51c465c4322d2ec233f6324e6b.tar.xz
golang-1.18-47ab3d4a42e9ab51c465c4322d2ec233f6324e6b.zip
Adding upstream version 1.18.10.upstream/1.18.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/api')
-rw-r--r--src/cmd/api/goapi.go1091
-rw-r--r--src/cmd/api/goapi_test.go230
-rw-r--r--src/cmd/api/run.go108
-rw-r--r--src/cmd/api/testdata/src/issue21181/dep/p.go5
-rw-r--r--src/cmd/api/testdata/src/issue21181/dep/p_amd64.go1
-rw-r--r--src/cmd/api/testdata/src/issue21181/indirect/p.go5
-rw-r--r--src/cmd/api/testdata/src/issue21181/p/p.go9
-rw-r--r--src/cmd/api/testdata/src/issue21181/p/p_amd64.go7
-rw-r--r--src/cmd/api/testdata/src/issue21181/p/p_generic.go12
-rw-r--r--src/cmd/api/testdata/src/issue29837/p/README1
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/golden.txt96
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/p1.go212
-rw-r--r--src/cmd/api/testdata/src/pkg/p2/golden.txt5
-rw-r--r--src/cmd/api/testdata/src/pkg/p2/p2.go9
-rw-r--r--src/cmd/api/testdata/src/pkg/p3/golden.txt3
-rw-r--r--src/cmd/api/testdata/src/pkg/p3/p3.go6
-rw-r--r--src/cmd/api/testdata/src/pkg/p4/golden.txt5
-rw-r--r--src/cmd/api/testdata/src/pkg/p4/p4.go26
18 files changed, 1831 insertions, 0 deletions
diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go
new file mode 100644
index 0000000..5ae059e
--- /dev/null
+++ b/src/cmd/api/goapi.go
@@ -0,0 +1,1091 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Binary api computes the exported API of a set of Go packages.
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/parser"
+ "go/token"
+ "go/types"
+ exec "internal/execabs"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "sort"
+ "strings"
+ "sync"
+)
+
+func goCmd() string {
+ var exeSuffix string
+ if runtime.GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+ path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+ if _, err := os.Stat(path); err == nil {
+ return path
+ }
+ return "go"
+}
+
+// Flags
+var (
+ checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against")
+ allowNew = flag.Bool("allow_new", true, "allow API additions")
+ exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
+ 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.")
+ verbose = flag.Bool("v", false, "verbose debugging")
+ forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
+)
+
+// contexts are the default contexts which are scanned, unless
+// overridden by the -contexts flag.
+var contexts = []*build.Context{
+ {GOOS: "linux", GOARCH: "386", CgoEnabled: true},
+ {GOOS: "linux", GOARCH: "386"},
+ {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true},
+ {GOOS: "linux", GOARCH: "amd64"},
+ {GOOS: "linux", GOARCH: "arm", CgoEnabled: true},
+ {GOOS: "linux", GOARCH: "arm"},
+ {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true},
+ {GOOS: "darwin", GOARCH: "amd64"},
+ {GOOS: "windows", GOARCH: "amd64"},
+ {GOOS: "windows", GOARCH: "386"},
+ {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true},
+ {GOOS: "freebsd", GOARCH: "386"},
+ {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true},
+ {GOOS: "freebsd", GOARCH: "amd64"},
+ {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true},
+ {GOOS: "freebsd", GOARCH: "arm"},
+ {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true},
+ {GOOS: "netbsd", GOARCH: "386"},
+ {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true},
+ {GOOS: "netbsd", GOARCH: "amd64"},
+ {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true},
+ {GOOS: "netbsd", GOARCH: "arm"},
+ {GOOS: "netbsd", GOARCH: "arm64", CgoEnabled: true},
+ {GOOS: "netbsd", GOARCH: "arm64"},
+ {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true},
+ {GOOS: "openbsd", GOARCH: "386"},
+ {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true},
+ {GOOS: "openbsd", GOARCH: "amd64"},
+}
+
+func contextName(c *build.Context) string {
+ s := c.GOOS + "-" + c.GOARCH
+ if c.CgoEnabled {
+ s += "-cgo"
+ }
+ if c.Dir != "" {
+ s += fmt.Sprintf(" [%s]", c.Dir)
+ }
+ return s
+}
+
+func parseContext(c string) *build.Context {
+ parts := strings.Split(c, "-")
+ if len(parts) < 2 {
+ log.Fatalf("bad context: %q", c)
+ }
+ bc := &build.Context{
+ GOOS: parts[0],
+ GOARCH: parts[1],
+ }
+ if len(parts) == 3 {
+ if parts[2] == "cgo" {
+ bc.CgoEnabled = true
+ } else {
+ log.Fatalf("bad context: %q", c)
+ }
+ }
+ return bc
+}
+
+func setContexts() {
+ contexts = []*build.Context{}
+ for _, c := range strings.Split(*forceCtx, ",") {
+ contexts = append(contexts, parseContext(c))
+ }
+}
+
+var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
+
+func main() {
+ flag.Parse()
+
+ if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
+ if *nextFile != "" {
+ fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
+ *nextFile = ""
+ }
+ }
+
+ if *forceCtx != "" {
+ setContexts()
+ }
+ for _, c := range contexts {
+ c.Compiler = build.Default.Compiler
+ }
+
+ walkers := make([]*Walker, len(contexts))
+ var wg sync.WaitGroup
+ for i, context := range contexts {
+ i, context := i, context
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ walkers[i] = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
+ }()
+ }
+ wg.Wait()
+
+ var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
+ for _, w := range walkers {
+ pkgNames := w.stdPackages
+ if flag.NArg() > 0 {
+ pkgNames = flag.Args()
+ }
+
+ for _, name := range pkgNames {
+ pkg, err := w.Import(name)
+ if _, nogo := err.(*build.NoGoError); nogo {
+ continue
+ }
+ if err != nil {
+ log.Fatalf("Import(%q): %v", name, err)
+ }
+ w.export(pkg)
+ }
+
+ ctxName := contextName(w.context)
+ for _, f := range w.Features() {
+ if featureCtx[f] == nil {
+ featureCtx[f] = make(map[string]bool)
+ }
+ featureCtx[f][ctxName] = true
+ }
+ }
+
+ var features []string
+ for f, cmap := range featureCtx {
+ if len(cmap) == len(contexts) {
+ features = append(features, f)
+ continue
+ }
+ comma := strings.Index(f, ",")
+ for cname := range cmap {
+ f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
+ features = append(features, f2)
+ }
+ }
+
+ fail := false
+ defer func() {
+ if fail {
+ os.Exit(1)
+ }
+ }()
+
+ bw := bufio.NewWriter(os.Stdout)
+ defer bw.Flush()
+
+ if *checkFile == "" {
+ sort.Strings(features)
+ for _, f := range features {
+ fmt.Fprintln(bw, f)
+ }
+ return
+ }
+
+ var required []string
+ for _, file := range strings.Split(*checkFile, ",") {
+ required = append(required, fileFeatures(file)...)
+ }
+ optional := fileFeatures(*nextFile)
+ exception := fileFeatures(*exceptFile)
+ fail = !compareAPI(bw, features, required, optional, exception, *allowNew)
+}
+
+// export emits the exported package features.
+func (w *Walker) export(pkg *types.Package) {
+ if *verbose {
+ log.Println(pkg)
+ }
+ pop := w.pushScope("pkg " + pkg.Path())
+ w.current = pkg
+ scope := pkg.Scope()
+ for _, name := range scope.Names() {
+ if token.IsExported(name) {
+ w.emitObj(scope.Lookup(name))
+ }
+ }
+ pop()
+}
+
+func set(items []string) map[string]bool {
+ s := make(map[string]bool)
+ for _, v := range items {
+ s[v] = true
+ }
+ return s
+}
+
+var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
+
+func featureWithoutContext(f string) string {
+ if !strings.Contains(f, "(") {
+ return f
+ }
+ return spaceParensRx.ReplaceAllString(f, "")
+}
+
+// portRemoved reports whether the given port-specific API feature is
+// okay to no longer exist because its port was removed.
+func portRemoved(feature string) bool {
+ return strings.Contains(feature, "(darwin-386)") ||
+ strings.Contains(feature, "(darwin-386-cgo)")
+}
+
+func compareAPI(w io.Writer, features, required, optional, exception []string, allowAdd bool) (ok bool) {
+ ok = true
+
+ optionalSet := set(optional)
+ exceptionSet := set(exception)
+ featureSet := set(features)
+
+ sort.Strings(features)
+ sort.Strings(required)
+
+ take := func(sl *[]string) string {
+ s := (*sl)[0]
+ *sl = (*sl)[1:]
+ return s
+ }
+
+ for len(required) > 0 || len(features) > 0 {
+ switch {
+ case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
+ feature := take(&required)
+ if exceptionSet[feature] {
+ // An "unfortunate" case: the feature was once
+ // included in the API (e.g. go1.txt), but was
+ // subsequently removed. These are already
+ // acknowledged by being in the file
+ // "api/except.txt". No need to print them out
+ // here.
+ } else if portRemoved(feature) {
+ // okay.
+ } else if featureSet[featureWithoutContext(feature)] {
+ // okay.
+ } else {
+ fmt.Fprintf(w, "-%s\n", feature)
+ ok = false // broke compatibility
+ }
+ case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
+ newFeature := take(&features)
+ if optionalSet[newFeature] {
+ // Known added feature to the upcoming release.
+ // Delete it from the map so we can detect any upcoming features
+ // which were never seen. (so we can clean up the nextFile)
+ delete(optionalSet, newFeature)
+ } else {
+ fmt.Fprintf(w, "+%s\n", newFeature)
+ if !allowAdd {
+ ok = false // we're in lock-down mode for next release
+ }
+ }
+ default:
+ take(&required)
+ take(&features)
+ }
+ }
+
+ // In next file, but not in API.
+ var missing []string
+ for feature := range optionalSet {
+ missing = append(missing, feature)
+ }
+ sort.Strings(missing)
+ for _, feature := range missing {
+ fmt.Fprintf(w, "±%s\n", feature)
+ }
+ return
+}
+
+// aliasReplacer applies type aliases to earlier API files,
+// to avoid misleading negative results.
+// This makes all the references to os.FileInfo in go1.txt
+// be read as if they said fs.FileInfo, since os.FileInfo is now an alias.
+// If there are many of these, we could do a more general solution,
+// but for now the replacer is fine.
+var aliasReplacer = strings.NewReplacer(
+ "os.FileInfo", "fs.FileInfo",
+ "os.FileMode", "fs.FileMode",
+ "os.PathError", "fs.PathError",
+)
+
+func fileFeatures(filename string) []string {
+ if filename == "" {
+ return nil
+ }
+ bs, err := os.ReadFile(filename)
+ if err != nil {
+ log.Fatalf("Error reading file %s: %v", filename, err)
+ }
+ s := string(bs)
+ s = aliasReplacer.Replace(s)
+ lines := strings.Split(s, "\n")
+ var nonblank []string
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if line != "" && !strings.HasPrefix(line, "#") {
+ nonblank = append(nonblank, line)
+ }
+ }
+ return nonblank
+}
+
+var fset = token.NewFileSet()
+
+type Walker struct {
+ context *build.Context
+ root string
+ scope []string
+ current *types.Package
+ features map[string]bool // set
+ imported map[string]*types.Package // packages already imported
+ stdPackages []string // names, omitting "unsafe", internal, and vendored packages
+ importMap map[string]map[string]string // importer dir -> import path -> canonical path
+ importDir map[string]string // canonical import path -> dir
+
+}
+
+func NewWalker(context *build.Context, root string) *Walker {
+ w := &Walker{
+ context: context,
+ root: root,
+ features: map[string]bool{},
+ imported: map[string]*types.Package{"unsafe": types.Unsafe},
+ }
+ w.loadImports()
+ return w
+}
+
+func (w *Walker) Features() (fs []string) {
+ for f := range w.features {
+ fs = append(fs, f)
+ }
+ sort.Strings(fs)
+ return
+}
+
+var parsedFileCache = make(map[string]*ast.File)
+
+func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
+ filename := filepath.Join(dir, file)
+ if f := parsedFileCache[filename]; f != nil {
+ return f, nil
+ }
+
+ f, err := parser.ParseFile(fset, filename, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+ parsedFileCache[filename] = f
+
+ return f, nil
+}
+
+// Disable before debugging non-obvious errors from the type-checker.
+const usePkgCache = true
+
+var (
+ pkgCache = map[string]*types.Package{} // map tagKey to package
+ pkgTags = map[string][]string{} // map import dir to list of relevant tags
+)
+
+// tagKey returns the tag-based key to use in the pkgCache.
+// It is a comma-separated string; the first part is dir, the rest tags.
+// The satisfied tags are derived from context but only those that
+// matter (the ones listed in the tags argument plus GOOS and GOARCH) are used.
+// The tags list, which came from go/build's Package.AllTags,
+// is known to be sorted.
+func tagKey(dir string, context *build.Context, tags []string) string {
+ ctags := map[string]bool{
+ context.GOOS: true,
+ context.GOARCH: true,
+ }
+ if context.CgoEnabled {
+ ctags["cgo"] = true
+ }
+ for _, tag := range context.BuildTags {
+ ctags[tag] = true
+ }
+ // TODO: ReleaseTags (need to load default)
+ key := dir
+
+ // explicit on GOOS and GOARCH as global cache will use "all" cached packages for
+ // an indirect imported package. See https://github.com/golang/go/issues/21181
+ // for more detail.
+ tags = append(tags, context.GOOS, context.GOARCH)
+ sort.Strings(tags)
+
+ for _, tag := range tags {
+ if ctags[tag] {
+ key += "," + tag
+ ctags[tag] = false
+ }
+ }
+ return key
+}
+
+type listImports struct {
+ stdPackages []string // names, omitting "unsafe", internal, and vendored packages
+ importDir map[string]string // canonical import path → directory
+ importMap map[string]map[string]string // import path → canonical import path
+}
+
+var listCache sync.Map // map[string]listImports, keyed by contextName
+
+// listSem is a semaphore restricting concurrent invocations of 'go list'. 'go
+// list' has its own internal concurrency, so we use a hard-coded constant (to
+// allow the I/O-intensive phases of 'go list' to overlap) instead of scaling
+// all the way up to GOMAXPROCS.
+var listSem = make(chan semToken, 2)
+
+type semToken struct{}
+
+// loadImports populates w with information about the packages in the standard
+// library and the packages they themselves import in w's build context.
+//
+// The source import path and expanded import path are identical except for vendored packages.
+// For example, on return:
+//
+// w.importMap["math"] = "math"
+// w.importDir["math"] = "<goroot>/src/math"
+//
+// w.importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route"
+// w.importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route"
+//
+// Since the set of packages that exist depends on context, the result of
+// loadImports also depends on context. However, to improve test running time
+// the configuration for each environment is cached across runs.
+func (w *Walker) loadImports() {
+ if w.context == nil {
+ return // test-only Walker; does not use the import map
+ }
+
+ name := contextName(w.context)
+
+ imports, ok := listCache.Load(name)
+ if !ok {
+ listSem <- semToken{}
+ defer func() { <-listSem }()
+
+ cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
+ cmd.Env = listEnv(w.context)
+ if w.context.Dir != "" {
+ cmd.Dir = w.context.Dir
+ }
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Fatalf("loading imports: %v\n%s", err, out)
+ }
+
+ var stdPackages []string
+ importMap := make(map[string]map[string]string)
+ importDir := make(map[string]string)
+ dec := json.NewDecoder(bytes.NewReader(out))
+ for {
+ var pkg struct {
+ ImportPath, Dir string
+ ImportMap map[string]string
+ Standard bool
+ }
+ err := dec.Decode(&pkg)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ log.Fatalf("go list: invalid output: %v", err)
+ }
+
+ // - Package "unsafe" contains special signatures requiring
+ // extra care when printing them - ignore since it is not
+ // going to change w/o a language change.
+ // - Internal and vendored packages do not contribute to our
+ // API surface. (If we are running within the "std" module,
+ // vendored dependencies appear as themselves instead of
+ // their "vendor/" standard-library copies.)
+ // - 'go list std' does not include commands, which cannot be
+ // imported anyway.
+ if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
+ stdPackages = append(stdPackages, ip)
+ }
+ importDir[pkg.ImportPath] = pkg.Dir
+ if len(pkg.ImportMap) > 0 {
+ importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
+ }
+ for k, v := range pkg.ImportMap {
+ importMap[pkg.Dir][k] = v
+ }
+ }
+
+ sort.Strings(stdPackages)
+ imports = listImports{
+ stdPackages: stdPackages,
+ importMap: importMap,
+ importDir: importDir,
+ }
+ imports, _ = listCache.LoadOrStore(name, imports)
+ }
+
+ li := imports.(listImports)
+ w.stdPackages = li.stdPackages
+ w.importDir = li.importDir
+ w.importMap = li.importMap
+}
+
+// listEnv returns the process environment to use when invoking 'go list' for
+// the given context.
+func listEnv(c *build.Context) []string {
+ if c == nil {
+ return os.Environ()
+ }
+
+ environ := append(os.Environ(),
+ "GOOS="+c.GOOS,
+ "GOARCH="+c.GOARCH)
+ if c.CgoEnabled {
+ environ = append(environ, "CGO_ENABLED=1")
+ } else {
+ environ = append(environ, "CGO_ENABLED=0")
+ }
+ return environ
+}
+
+// Importing is a sentinel taking the place in Walker.imported
+// for a package that is in the process of being imported.
+var importing types.Package
+
+func (w *Walker) Import(name string) (*types.Package, error) {
+ return w.ImportFrom(name, "", 0)
+}
+
+func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
+ name := fromPath
+ if canonical, ok := w.importMap[fromDir][fromPath]; ok {
+ name = canonical
+ }
+
+ pkg := w.imported[name]
+ if pkg != nil {
+ if pkg == &importing {
+ log.Fatalf("cycle importing package %q", name)
+ }
+ return pkg, nil
+ }
+ w.imported[name] = &importing
+
+ // Determine package files.
+ dir := w.importDir[name]
+ if dir == "" {
+ dir = filepath.Join(w.root, filepath.FromSlash(name))
+ }
+ if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
+ log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
+ }
+
+ context := w.context
+ if context == nil {
+ context = &build.Default
+ }
+
+ // Look in cache.
+ // If we've already done an import with the same set
+ // of relevant tags, reuse the result.
+ var key string
+ if usePkgCache {
+ if tags, ok := pkgTags[dir]; ok {
+ key = tagKey(dir, context, tags)
+ if pkg := pkgCache[key]; pkg != nil {
+ w.imported[name] = pkg
+ return pkg, nil
+ }
+ }
+ }
+
+ info, err := context.ImportDir(dir, 0)
+ if err != nil {
+ if _, nogo := err.(*build.NoGoError); nogo {
+ return nil, err
+ }
+ log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
+ }
+
+ // Save tags list first time we see a directory.
+ if usePkgCache {
+ if _, ok := pkgTags[dir]; !ok {
+ pkgTags[dir] = info.AllTags
+ key = tagKey(dir, context, info.AllTags)
+ }
+ }
+
+ filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
+
+ // Parse package files.
+ var files []*ast.File
+ for _, file := range filenames {
+ f, err := w.parseFile(dir, file)
+ if err != nil {
+ log.Fatalf("error parsing package %s: %s", name, err)
+ }
+ files = append(files, f)
+ }
+
+ // Type-check package files.
+ var sizes types.Sizes
+ if w.context != nil {
+ sizes = types.SizesFor(w.context.Compiler, w.context.GOARCH)
+ }
+ conf := types.Config{
+ IgnoreFuncBodies: true,
+ FakeImportC: true,
+ Importer: w,
+ Sizes: sizes,
+ }
+ pkg, err = conf.Check(name, fset, files, nil)
+ if err != nil {
+ ctxt := "<no context>"
+ if w.context != nil {
+ ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
+ }
+ log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
+ }
+
+ if usePkgCache {
+ pkgCache[key] = pkg
+ }
+
+ w.imported[name] = pkg
+ return pkg, nil
+}
+
+// pushScope enters a new scope (walking a package, type, node, etc)
+// and returns a function that will leave the scope (with sanity checking
+// for mismatched pushes & pops)
+func (w *Walker) pushScope(name string) (popFunc func()) {
+ w.scope = append(w.scope, name)
+ return func() {
+ if len(w.scope) == 0 {
+ log.Fatalf("attempt to leave scope %q with empty scope list", name)
+ }
+ if w.scope[len(w.scope)-1] != name {
+ log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
+ }
+ w.scope = w.scope[:len(w.scope)-1]
+ }
+}
+
+func sortedMethodNames(typ *types.Interface) []string {
+ n := typ.NumMethods()
+ list := make([]string, n)
+ for i := range list {
+ list[i] = typ.Method(i).Name()
+ }
+ sort.Strings(list)
+ return list
+}
+
+// sortedEmbeddeds returns constraint types embedded in an
+// interface. It does not include embedded interface types or methods.
+func (w *Walker) sortedEmbeddeds(typ *types.Interface) []string {
+ n := typ.NumEmbeddeds()
+ list := make([]string, 0, n)
+ for i := 0; i < n; i++ {
+ emb := typ.EmbeddedType(i)
+ switch emb := emb.(type) {
+ case *types.Interface:
+ list = append(list, w.sortedEmbeddeds(emb)...)
+ case *types.Union:
+ var buf bytes.Buffer
+ nu := emb.Len()
+ for i := 0; i < nu; i++ {
+ if i > 0 {
+ buf.WriteString(" | ")
+ }
+ term := emb.Term(i)
+ if term.Tilde() {
+ buf.WriteByte('~')
+ }
+ w.writeType(&buf, term.Type())
+ }
+ list = append(list, buf.String())
+ }
+ }
+ sort.Strings(list)
+ return list
+}
+
+func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
+ switch typ := typ.(type) {
+ case *types.Basic:
+ s := typ.Name()
+ switch typ.Kind() {
+ case types.UnsafePointer:
+ s = "unsafe.Pointer"
+ case types.UntypedBool:
+ s = "ideal-bool"
+ case types.UntypedInt:
+ s = "ideal-int"
+ case types.UntypedRune:
+ // "ideal-char" for compatibility with old tool
+ // TODO(gri) change to "ideal-rune"
+ s = "ideal-char"
+ case types.UntypedFloat:
+ s = "ideal-float"
+ case types.UntypedComplex:
+ s = "ideal-complex"
+ case types.UntypedString:
+ s = "ideal-string"
+ case types.UntypedNil:
+ panic("should never see untyped nil type")
+ default:
+ switch s {
+ case "byte":
+ s = "uint8"
+ case "rune":
+ s = "int32"
+ }
+ }
+ buf.WriteString(s)
+
+ case *types.Array:
+ fmt.Fprintf(buf, "[%d]", typ.Len())
+ w.writeType(buf, typ.Elem())
+
+ case *types.Slice:
+ buf.WriteString("[]")
+ w.writeType(buf, typ.Elem())
+
+ case *types.Struct:
+ buf.WriteString("struct")
+
+ case *types.Pointer:
+ buf.WriteByte('*')
+ w.writeType(buf, typ.Elem())
+
+ case *types.Tuple:
+ panic("should never see a tuple type")
+
+ case *types.Signature:
+ buf.WriteString("func")
+ w.writeSignature(buf, typ)
+
+ case *types.Interface:
+ buf.WriteString("interface{")
+ if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
+ buf.WriteByte(' ')
+ }
+ if typ.NumMethods() > 0 {
+ buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
+ }
+ if typ.NumEmbeddeds() > 0 {
+ buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", "))
+ }
+ if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
+ buf.WriteByte(' ')
+ }
+ buf.WriteString("}")
+
+ case *types.Map:
+ buf.WriteString("map[")
+ w.writeType(buf, typ.Key())
+ buf.WriteByte(']')
+ w.writeType(buf, typ.Elem())
+
+ case *types.Chan:
+ var s string
+ switch typ.Dir() {
+ case types.SendOnly:
+ s = "chan<- "
+ case types.RecvOnly:
+ s = "<-chan "
+ case types.SendRecv:
+ s = "chan "
+ default:
+ panic("unreachable")
+ }
+ buf.WriteString(s)
+ w.writeType(buf, typ.Elem())
+
+ case *types.Named:
+ obj := typ.Obj()
+ pkg := obj.Pkg()
+ if pkg != nil && pkg != w.current {
+ buf.WriteString(pkg.Name())
+ buf.WriteByte('.')
+ }
+ buf.WriteString(typ.Obj().Name())
+
+ case *types.TypeParam:
+ // Type parameter names may change, so use a placeholder instead.
+ fmt.Fprintf(buf, "$%d", typ.Index())
+
+ default:
+ panic(fmt.Sprintf("unknown type %T", typ))
+ }
+}
+
+func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
+ if tparams := sig.TypeParams(); tparams != nil {
+ w.writeTypeParams(buf, tparams, true)
+ }
+ w.writeParams(buf, sig.Params(), sig.Variadic())
+ switch res := sig.Results(); res.Len() {
+ case 0:
+ // nothing to do
+ case 1:
+ buf.WriteByte(' ')
+ w.writeType(buf, res.At(0).Type())
+ default:
+ buf.WriteByte(' ')
+ w.writeParams(buf, res, false)
+ }
+}
+
+func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
+ buf.WriteByte('[')
+ c := tparams.Len()
+ for i := 0; i < c; i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ tp := tparams.At(i)
+ w.writeType(buf, tp)
+ if withConstraints {
+ buf.WriteByte(' ')
+ w.writeType(buf, tp.Constraint())
+ }
+ }
+ buf.WriteByte(']')
+}
+
+func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
+ buf.WriteByte('(')
+ for i, n := 0, t.Len(); i < n; i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ typ := t.At(i).Type()
+ if variadic && i+1 == n {
+ buf.WriteString("...")
+ typ = typ.(*types.Slice).Elem()
+ }
+ w.writeType(buf, typ)
+ }
+ buf.WriteByte(')')
+}
+
+func (w *Walker) typeString(typ types.Type) string {
+ var buf bytes.Buffer
+ w.writeType(&buf, typ)
+ return buf.String()
+}
+
+func (w *Walker) signatureString(sig *types.Signature) string {
+ var buf bytes.Buffer
+ w.writeSignature(&buf, sig)
+ return buf.String()
+}
+
+func (w *Walker) emitObj(obj types.Object) {
+ switch obj := obj.(type) {
+ case *types.Const:
+ w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
+ x := obj.Val()
+ short := x.String()
+ exact := x.ExactString()
+ if short == exact {
+ w.emitf("const %s = %s", obj.Name(), short)
+ } else {
+ w.emitf("const %s = %s // %s", obj.Name(), short, exact)
+ }
+ case *types.Var:
+ w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
+ case *types.TypeName:
+ w.emitType(obj)
+ case *types.Func:
+ w.emitFunc(obj)
+ default:
+ panic("unknown object: " + obj.String())
+ }
+}
+
+func (w *Walker) emitType(obj *types.TypeName) {
+ name := obj.Name()
+ if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
+ var buf bytes.Buffer
+ buf.WriteString(name)
+ w.writeTypeParams(&buf, tparams, true)
+ name = buf.String()
+ }
+ typ := obj.Type()
+ if obj.IsAlias() {
+ w.emitf("type %s = %s", name, w.typeString(typ))
+ return
+ }
+ switch typ := typ.Underlying().(type) {
+ case *types.Struct:
+ w.emitStructType(name, typ)
+ case *types.Interface:
+ w.emitIfaceType(name, typ)
+ return // methods are handled by emitIfaceType
+ default:
+ w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
+ }
+
+ // emit methods with value receiver
+ var methodNames map[string]bool
+ vset := types.NewMethodSet(typ)
+ for i, n := 0, vset.Len(); i < n; i++ {
+ m := vset.At(i)
+ if m.Obj().Exported() {
+ w.emitMethod(m)
+ if methodNames == nil {
+ methodNames = make(map[string]bool)
+ }
+ methodNames[m.Obj().Name()] = true
+ }
+ }
+
+ // emit methods with pointer receiver; exclude
+ // methods that we have emitted already
+ // (the method set of *T includes the methods of T)
+ pset := types.NewMethodSet(types.NewPointer(typ))
+ for i, n := 0, pset.Len(); i < n; i++ {
+ m := pset.At(i)
+ if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
+ w.emitMethod(m)
+ }
+ }
+}
+
+func (w *Walker) emitStructType(name string, typ *types.Struct) {
+ typeStruct := fmt.Sprintf("type %s struct", name)
+ w.emitf(typeStruct)
+ defer w.pushScope(typeStruct)()
+
+ for i := 0; i < typ.NumFields(); i++ {
+ f := typ.Field(i)
+ if !f.Exported() {
+ continue
+ }
+ typ := f.Type()
+ if f.Anonymous() {
+ w.emitf("embedded %s", w.typeString(typ))
+ continue
+ }
+ w.emitf("%s %s", f.Name(), w.typeString(typ))
+ }
+}
+
+func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
+ pop := w.pushScope("type " + name + " interface")
+
+ var methodNames []string
+ complete := true
+ mset := types.NewMethodSet(typ)
+ for i, n := 0, mset.Len(); i < n; i++ {
+ m := mset.At(i).Obj().(*types.Func)
+ if !m.Exported() {
+ complete = false
+ continue
+ }
+ methodNames = append(methodNames, m.Name())
+ w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
+ }
+
+ if !complete {
+ // The method set has unexported methods, so all the
+ // implementations are provided by the same package,
+ // so the method set can be extended. Instead of recording
+ // the full set of names (below), record only that there were
+ // unexported methods. (If the interface shrinks, we will notice
+ // because a method signature emitted during the last loop
+ // will disappear.)
+ w.emitf("unexported methods")
+ }
+
+ pop()
+
+ if !complete {
+ return
+ }
+
+ if len(methodNames) == 0 {
+ w.emitf("type %s interface {}", name)
+ return
+ }
+
+ sort.Strings(methodNames)
+ w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
+}
+
+func (w *Walker) emitFunc(f *types.Func) {
+ sig := f.Type().(*types.Signature)
+ if sig.Recv() != nil {
+ panic("method considered a regular function: " + f.String())
+ }
+ w.emitf("func %s%s", f.Name(), w.signatureString(sig))
+}
+
+func (w *Walker) emitMethod(m *types.Selection) {
+ sig := m.Type().(*types.Signature)
+ recv := sig.Recv().Type()
+ // report exported methods with unexported receiver base type
+ if true {
+ base := recv
+ if p, _ := recv.(*types.Pointer); p != nil {
+ base = p.Elem()
+ }
+ if obj := base.(*types.Named).Obj(); !obj.Exported() {
+ log.Fatalf("exported method with unexported receiver base type: %s", m)
+ }
+ }
+ tps := ""
+ if rtp := sig.RecvTypeParams(); rtp != nil {
+ var buf bytes.Buffer
+ w.writeTypeParams(&buf, rtp, false)
+ tps = buf.String()
+ }
+ w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
+}
+
+func (w *Walker) emitf(format string, args ...any) {
+ f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
+ if strings.Contains(f, "\n") {
+ panic("feature contains newlines: " + f)
+ }
+
+ if _, dup := w.features[f]; dup {
+ panic("duplicate feature inserted: " + f)
+ }
+ w.features[f] = true
+
+ if *verbose {
+ log.Printf("feature: %s", f)
+ }
+}
diff --git a/src/cmd/api/goapi_test.go b/src/cmd/api/goapi_test.go
new file mode 100644
index 0000000..16e0058
--- /dev/null
+++ b/src/cmd/api/goapi_test.go
@@ -0,0 +1,230 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/build"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "sync"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ flag.Parse()
+ for _, c := range contexts {
+ c.Compiler = build.Default.Compiler
+ }
+
+ // Warm up the import cache in parallel.
+ var wg sync.WaitGroup
+ for _, context := range contexts {
+ context := context
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _ = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
+ }()
+ }
+ wg.Wait()
+
+ os.Exit(m.Run())
+}
+
+var (
+ updateGolden = flag.Bool("updategolden", false, "update golden files")
+)
+
+func TestGolden(t *testing.T) {
+ td, err := os.Open("testdata/src/pkg")
+ if err != nil {
+ t.Fatal(err)
+ }
+ fis, err := td.Readdir(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, fi := range fis {
+ if !fi.IsDir() {
+ continue
+ }
+
+ // TODO(gri) remove extra pkg directory eventually
+ goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt")
+ w := NewWalker(nil, "testdata/src/pkg")
+ pkg, _ := w.Import(fi.Name())
+ w.export(pkg)
+
+ if *updateGolden {
+ os.Remove(goldenFile)
+ f, err := os.Create(goldenFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, feat := range w.Features() {
+ fmt.Fprintf(f, "%s\n", feat)
+ }
+ f.Close()
+ }
+
+ bs, err := os.ReadFile(goldenFile)
+ if err != nil {
+ t.Fatalf("opening golden.txt for package %q: %v", fi.Name(), err)
+ }
+ wanted := strings.Split(string(bs), "\n")
+ sort.Strings(wanted)
+ for _, feature := range wanted {
+ if feature == "" {
+ continue
+ }
+ _, ok := w.features[feature]
+ if !ok {
+ t.Errorf("package %s: missing feature %q", fi.Name(), feature)
+ }
+ delete(w.features, feature)
+ }
+
+ for _, feature := range w.Features() {
+ t.Errorf("package %s: extra feature not in golden file: %q", fi.Name(), feature)
+ }
+ }
+}
+
+func TestCompareAPI(t *testing.T) {
+ tests := []struct {
+ name string
+ features, required, optional, exception []string
+ ok bool // want
+ out string // want
+ }{
+ {
+ name: "feature added",
+ features: []string{"A", "B", "C", "D", "E", "F"},
+ required: []string{"B", "D"},
+ ok: true,
+ out: "+A\n+C\n+E\n+F\n",
+ },
+ {
+ name: "feature removed",
+ features: []string{"C", "A"},
+ required: []string{"A", "B", "C"},
+ ok: false,
+ out: "-B\n",
+ },
+ {
+ name: "feature added then removed",
+ features: []string{"A", "C"},
+ optional: []string{"B"},
+ required: []string{"A", "C"},
+ ok: true,
+ out: "±B\n",
+ },
+ {
+ name: "exception removal",
+ required: []string{"A", "B", "C"},
+ features: []string{"A", "C"},
+ exception: []string{"B"},
+ ok: true,
+ out: "",
+ },
+ {
+ // https://golang.org/issue/4303
+ name: "contexts reconverging",
+ required: []string{
+ "A",
+ "pkg syscall (darwin-amd64), type RawSockaddrInet6 struct",
+ },
+ features: []string{
+ "A",
+ "pkg syscall, type RawSockaddrInet6 struct",
+ },
+ ok: true,
+ out: "+pkg syscall, type RawSockaddrInet6 struct\n",
+ },
+ }
+ for _, tt := range tests {
+ buf := new(bytes.Buffer)
+ gotok := compareAPI(buf, tt.features, tt.required, tt.optional, tt.exception, true)
+ if gotok != tt.ok {
+ t.Errorf("%s: ok = %v; want %v", tt.name, gotok, tt.ok)
+ }
+ if got := buf.String(); got != tt.out {
+ t.Errorf("%s: output differs\nGOT:\n%s\nWANT:\n%s", tt.name, got, tt.out)
+ }
+ }
+}
+
+func TestSkipInternal(t *testing.T) {
+ tests := []struct {
+ pkg string
+ want bool
+ }{
+ {"net/http", true},
+ {"net/http/internal-foo", true},
+ {"net/http/internal", false},
+ {"net/http/internal/bar", false},
+ {"internal/foo", false},
+ {"internal", false},
+ }
+ for _, tt := range tests {
+ got := !internalPkg.MatchString(tt.pkg)
+ if got != tt.want {
+ t.Errorf("%s is internal = %v; want %v", tt.pkg, got, tt.want)
+ }
+ }
+}
+
+func BenchmarkAll(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, context := range contexts {
+ w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
+ for _, name := range w.stdPackages {
+ pkg, _ := w.Import(name)
+ w.export(pkg)
+ }
+ w.Features()
+ }
+ }
+}
+
+func TestIssue21181(t *testing.T) {
+ for _, context := range contexts {
+ w := NewWalker(context, "testdata/src/issue21181")
+ pkg, err := w.Import("p")
+ if err != nil {
+ t.Fatalf("%s: (%s-%s) %s %v", err, context.GOOS, context.GOARCH,
+ pkg.Name(), w.imported)
+ }
+ w.export(pkg)
+ }
+}
+
+func TestIssue29837(t *testing.T) {
+ for _, context := range contexts {
+ w := NewWalker(context, "testdata/src/issue29837")
+ _, err := w.Import("p")
+ if _, nogo := err.(*build.NoGoError); !nogo {
+ t.Errorf("expected *build.NoGoError, got %T", err)
+ }
+ }
+}
+
+func TestIssue41358(t *testing.T) {
+ context := new(build.Context)
+ *context = build.Default
+ context.Dir = filepath.Join(context.GOROOT, "src")
+
+ w := NewWalker(context, context.Dir)
+ for _, pkg := range w.stdPackages {
+ if strings.HasPrefix(pkg, "vendor/") || strings.HasPrefix(pkg, "golang.org/x/") {
+ t.Fatalf("stdPackages contains unexpected package %s", pkg)
+ }
+ }
+}
diff --git a/src/cmd/api/run.go b/src/cmd/api/run.go
new file mode 100644
index 0000000..1b94a1b
--- /dev/null
+++ b/src/cmd/api/run.go
@@ -0,0 +1,108 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// The run program is invoked via the dist tool.
+// To invoke manually: go tool dist test -run api --no-rebuild
+package main
+
+import (
+ "errors"
+ "fmt"
+ exec "internal/execabs"
+ "internal/goversion"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+func goCmd() string {
+ var exeSuffix string
+ if runtime.GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+ path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+ if _, err := os.Stat(path); err == nil {
+ return path
+ }
+ return "go"
+}
+
+var goroot string
+
+func main() {
+ log.SetFlags(0)
+ goroot = os.Getenv("GOROOT") // should be set by run.{bash,bat}
+ if goroot == "" {
+ log.Fatal("No $GOROOT set.")
+ }
+
+ apiDir := filepath.Join(goroot, "api")
+ out, err := exec.Command(goCmd(), "tool", "api",
+ "-c", findAPIDirFiles(apiDir),
+ allowNew(apiDir),
+ "-next", filepath.Join(apiDir, "next.txt"),
+ "-except", filepath.Join(apiDir, "except.txt")).CombinedOutput()
+ if err != nil {
+ log.Fatalf("Error running API checker: %v\n%s", err, out)
+ }
+ fmt.Print(string(out))
+}
+
+// findAPIDirFiles returns a comma-separated list of Go API files
+// (go1.txt, go1.1.txt, etc.) located in apiDir.
+func findAPIDirFiles(apiDir string) string {
+ dir, err := os.Open(apiDir)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer dir.Close()
+ fs, err := dir.Readdirnames(-1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ var apiFiles []string
+ for _, fn := range fs {
+ if strings.HasPrefix(fn, "go1") {
+ apiFiles = append(apiFiles, filepath.Join(apiDir, fn))
+ }
+ }
+ return strings.Join(apiFiles, ",")
+}
+
+// allowNew returns the -allow_new flag to use for the 'go tool api' invocation.
+func allowNew(apiDir string) string {
+ // Verify that the api/go1.n.txt for previous Go version exists.
+ // It definitely should, otherwise it's a signal that the logic below may be outdated.
+ if _, err := os.Stat(filepath.Join(apiDir, fmt.Sprintf("go1.%d.txt", goversion.Version-1))); err != nil {
+ log.Fatalln("Problem with api file for previous release:", err)
+ }
+
+ // See whether the api/go1.n.txt for this Go version has been created.
+ // (As of April 2021, it gets created during the release of the first Beta.)
+ _, err := os.Stat(filepath.Join(apiDir, fmt.Sprintf("go1.%d.txt", goversion.Version)))
+ if errors.Is(err, fs.ErrNotExist) {
+ // It doesn't exist, so we're in development or before Beta 1.
+ // At this stage, unmentioned API additions are deemed okay.
+ // (They will be quietly shown in API check output, but the test won't fail).
+ return "-allow_new=true"
+ } else if err == nil {
+ // The api/go1.n.txt for this Go version has been created,
+ // so we're definitely past Beta 1 in the release cycle.
+ //
+ // From this point, enforce that api/go1.n.txt is an accurate and complete
+ // representation of what's going into the release by failing API check if
+ // there are API additions (a month into the freeze, there shouldn't be many).
+ //
+ // See golang.org/issue/43956.
+ return "-allow_new=false"
+ } else {
+ log.Fatal(err)
+ }
+ panic("unreachable")
+}
diff --git a/src/cmd/api/testdata/src/issue21181/dep/p.go b/src/cmd/api/testdata/src/issue21181/dep/p.go
new file mode 100644
index 0000000..2d8e0c4
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/dep/p.go
@@ -0,0 +1,5 @@
+package dep
+
+type Interface interface {
+ N([]byte)
+}
diff --git a/src/cmd/api/testdata/src/issue21181/dep/p_amd64.go b/src/cmd/api/testdata/src/issue21181/dep/p_amd64.go
new file mode 100644
index 0000000..8a2343a
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/dep/p_amd64.go
@@ -0,0 +1 @@
+package dep
diff --git a/src/cmd/api/testdata/src/issue21181/indirect/p.go b/src/cmd/api/testdata/src/issue21181/indirect/p.go
new file mode 100644
index 0000000..e37cf3f
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/indirect/p.go
@@ -0,0 +1,5 @@
+package indirect
+
+import "dep"
+
+func F(dep.Interface) {}
diff --git a/src/cmd/api/testdata/src/issue21181/p/p.go b/src/cmd/api/testdata/src/issue21181/p/p.go
new file mode 100644
index 0000000..a704160
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/p/p.go
@@ -0,0 +1,9 @@
+package p
+
+import (
+ "dep"
+)
+
+type algo struct {
+ indrt func(dep.Interface)
+}
diff --git a/src/cmd/api/testdata/src/issue21181/p/p_amd64.go b/src/cmd/api/testdata/src/issue21181/p/p_amd64.go
new file mode 100644
index 0000000..02b4cbf
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/p/p_amd64.go
@@ -0,0 +1,7 @@
+package p
+
+import "indirect"
+
+var in = []algo{
+ {indirect.F},
+}
diff --git a/src/cmd/api/testdata/src/issue21181/p/p_generic.go b/src/cmd/api/testdata/src/issue21181/p/p_generic.go
new file mode 100644
index 0000000..ad6df20
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue21181/p/p_generic.go
@@ -0,0 +1,12 @@
+//go:build !amd64
+// +build !amd64
+
+package p
+
+import (
+ "indirect"
+)
+
+var in = []algo{
+ {indirect.F},
+}
diff --git a/src/cmd/api/testdata/src/issue29837/p/README b/src/cmd/api/testdata/src/issue29837/p/README
new file mode 100644
index 0000000..770bc0f
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue29837/p/README
@@ -0,0 +1 @@
+Empty directory for test, see https://golang.org/issues/29837. \ No newline at end of file
diff --git a/src/cmd/api/testdata/src/pkg/p1/golden.txt b/src/cmd/api/testdata/src/pkg/p1/golden.txt
new file mode 100644
index 0000000..0378a56
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p1/golden.txt
@@ -0,0 +1,96 @@
+pkg p1, const A = 1
+pkg p1, const A ideal-int
+pkg p1, const A64 = 1
+pkg p1, const A64 int64
+pkg p1, const AIsLowerA = 11
+pkg p1, const AIsLowerA ideal-int
+pkg p1, const B0 = 2
+pkg p1, const B0 ideal-int
+pkg p1, const ConstChase2 = 11
+pkg p1, const ConstChase2 ideal-int
+pkg p1, const ConversionConst = 5
+pkg p1, const ConversionConst MyInt
+pkg p1, const FloatConst = 1.5 // 3/2
+pkg p1, const FloatConst ideal-float
+pkg p1, const StrConst = "foo"
+pkg p1, const StrConst ideal-string
+pkg p1, func Bar(int8, int16, int64)
+pkg p1, func Bar1(int8, int16, int64) uint64
+pkg p1, func Bar2(int8, int16, int64) (uint8, uint64)
+pkg p1, func BarE() Error
+pkg p1, func Now() Time
+pkg p1, func PlainFunc(int, int, string) (*B, error)
+pkg p1, func TakesFunc(func(int) int)
+pkg p1, method (*B) JustOnB()
+pkg p1, method (*B) OnBothTandBPtr()
+pkg p1, method (*Embedded) OnEmbedded()
+pkg p1, method (*S2) SMethod(int8, int16, int64)
+pkg p1, method (*T) JustOnT()
+pkg p1, method (*T) OnBothTandBPtr()
+pkg p1, method (B) OnBothTandBVal()
+pkg p1, method (S) StructValueMethod()
+pkg p1, method (S) StructValueMethodNamedRecv()
+pkg p1, method (S2) StructValueMethod()
+pkg p1, method (S2) StructValueMethodNamedRecv()
+pkg p1, method (T) OnBothTandBVal()
+pkg p1, method (TPtrExported) OnEmbedded()
+pkg p1, method (TPtrUnexported) OnBothTandBPtr()
+pkg p1, method (TPtrUnexported) OnBothTandBVal()
+pkg p1, type B struct
+pkg p1, type ByteStruct struct
+pkg p1, type ByteStruct struct, B uint8
+pkg p1, type ByteStruct struct, R int32
+pkg p1, type Codec struct
+pkg p1, type Codec struct, Func func(int, int) int
+pkg p1, type EmbedSelector struct
+pkg p1, type EmbedSelector struct, embedded Time
+pkg p1, type EmbedURLPtr struct
+pkg p1, type EmbedURLPtr struct, embedded *URL
+pkg p1, type Embedded struct
+pkg p1, type Error interface { Error, Temporary }
+pkg p1, type Error interface, Error() string
+pkg p1, type Error interface, Temporary() bool
+pkg p1, type FuncType func(int, int, string) (*B, error)
+pkg p1, type I interface, Get(string) int64
+pkg p1, type I interface, GetNamed(string) int64
+pkg p1, type I interface, Name() string
+pkg p1, type I interface, PackageTwoMeth()
+pkg p1, type I interface, Set(string, int64)
+pkg p1, type I interface, unexported methods
+pkg p1, type MyInt int
+pkg p1, type Namer interface { Name }
+pkg p1, type Namer interface, Name() string
+pkg p1, type Private interface, X()
+pkg p1, type Private interface, unexported methods
+pkg p1, type Public interface { X, Y }
+pkg p1, type Public interface, X()
+pkg p1, type Public interface, Y()
+pkg p1, type S struct
+pkg p1, type S struct, Public *int
+pkg p1, type S struct, PublicTime Time
+pkg p1, type S2 struct
+pkg p1, type S2 struct, Extra bool
+pkg p1, type S2 struct, embedded S
+pkg p1, type SI struct
+pkg p1, type SI struct, I int
+pkg p1, type T struct
+pkg p1, type TPtrExported struct
+pkg p1, type TPtrExported struct, embedded *Embedded
+pkg p1, type TPtrUnexported struct
+pkg p1, type Time struct
+pkg p1, type URL struct
+pkg p1, var Byte uint8
+pkg p1, var ByteConv []uint8
+pkg p1, var ByteFunc func(uint8) int32
+pkg p1, var ChecksumError error
+pkg p1, var SIPtr *SI
+pkg p1, var SIPtr2 *SI
+pkg p1, var SIVal SI
+pkg p1, var StrConv string
+pkg p1, var V string
+pkg p1, var V1 uint64
+pkg p1, var V2 p2.Twoer
+pkg p1, var VError Error
+pkg p1, var X I
+pkg p1, var X0 int64
+pkg p1, var Y int
diff --git a/src/cmd/api/testdata/src/pkg/p1/p1.go b/src/cmd/api/testdata/src/pkg/p1/p1.go
new file mode 100644
index 0000000..81826d7
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p1/p1.go
@@ -0,0 +1,212 @@
+package p1
+
+import (
+ ptwo "p2"
+)
+
+const (
+ ConstChase2 = constChase // forward declaration to unexported ident
+ constChase = AIsLowerA // forward declaration to exported ident
+
+ A = 1
+ a = 11
+ A64 int64 = 1
+
+ AIsLowerA = a // previously declared
+)
+
+const (
+ ConversionConst = MyInt(5)
+)
+
+// Variables from function calls.
+var (
+ V = ptwo.F()
+ VError = BarE()
+ V1 = Bar1(1, 2, 3)
+ V2 = ptwo.G()
+)
+
+// Variables with conversions:
+var (
+ StrConv = string("foo")
+ ByteConv = []byte("foo")
+)
+
+var ChecksumError = ptwo.NewError("gzip checksum error")
+
+const B0 = 2
+const StrConst = "foo"
+const FloatConst = 1.5
+
+type myInt int
+
+type MyInt int
+
+type Time struct{}
+
+type S struct {
+ Public *int
+ private *int
+ PublicTime Time
+}
+
+type URL struct{}
+
+type EmbedURLPtr struct {
+ *URL
+}
+
+type S2 struct {
+ S
+ Extra bool
+}
+
+var X0 int64
+
+var (
+ Y int
+ X I
+)
+
+type Namer interface {
+ Name() string
+}
+
+type I interface {
+ Namer
+ ptwo.Twoer
+ Set(name string, balance int64)
+ Get(string) int64
+ GetNamed(string) (balance int64)
+ private()
+}
+
+type Public interface {
+ X()
+ Y()
+}
+
+type Private interface {
+ X()
+ y()
+}
+
+type Error interface {
+ error
+ Temporary() bool
+}
+
+func (myInt) privateTypeMethod() {}
+func (myInt) CapitalMethodUnexportedType() {}
+
+func (s *S2) SMethod(x int8, y int16, z int64) {}
+
+type s struct{}
+
+func (s) method()
+func (s) Method()
+
+func (S) StructValueMethod()
+func (ignored S) StructValueMethodNamedRecv()
+
+func (s *S2) unexported(x int8, y int16, z int64) {}
+
+func Bar(x int8, y int16, z int64) {}
+func Bar1(x int8, y int16, z int64) uint64 {}
+func Bar2(x int8, y int16, z int64) (uint8, uint64) {}
+func BarE() Error {}
+
+func unexported(x int8, y int16, z int64) {}
+
+func TakesFunc(f func(dontWantName int) int)
+
+type Codec struct {
+ Func func(x int, y int) (z int)
+}
+
+type SI struct {
+ I int
+}
+
+var SIVal = SI{}
+var SIPtr = &SI{}
+var SIPtr2 *SI
+
+type T struct {
+ common
+}
+
+type B struct {
+ common
+}
+
+type common struct {
+ i int
+}
+
+type TPtrUnexported struct {
+ *common
+}
+
+type TPtrExported struct {
+ *Embedded
+}
+
+type FuncType func(x, y int, s string) (b *B, err error)
+
+type Embedded struct{}
+
+func PlainFunc(x, y int, s string) (b *B, err error)
+
+func (*Embedded) OnEmbedded() {}
+
+func (*T) JustOnT() {}
+func (*B) JustOnB() {}
+func (*common) OnBothTandBPtr() {}
+func (common) OnBothTandBVal() {}
+
+type EmbedSelector struct {
+ Time
+}
+
+const (
+ foo = "foo"
+ foo2 string = "foo2"
+ truth = foo == "foo" || foo2 == "foo2"
+)
+
+func ellipsis(...string) {}
+
+func Now() Time {
+ var now Time
+ return now
+}
+
+var x = &S{
+ Public: nil,
+ private: nil,
+ PublicTime: Now(),
+}
+
+var parenExpr = (1 + 5)
+
+var funcLit = func() {}
+
+var m map[string]int
+
+var chanVar chan int
+
+var ifaceVar any = 5
+
+var assertVar = ifaceVar.(int)
+
+var indexVar = m["foo"]
+
+var Byte byte
+var ByteFunc func(byte) rune
+
+type ByteStruct struct {
+ B byte
+ R rune
+}
diff --git a/src/cmd/api/testdata/src/pkg/p2/golden.txt b/src/cmd/api/testdata/src/pkg/p2/golden.txt
new file mode 100644
index 0000000..4271620
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p2/golden.txt
@@ -0,0 +1,5 @@
+pkg p2, func F() string
+pkg p2, func G() Twoer
+pkg p2, func NewError(string) error
+pkg p2, type Twoer interface { PackageTwoMeth }
+pkg p2, type Twoer interface, PackageTwoMeth()
diff --git a/src/cmd/api/testdata/src/pkg/p2/p2.go b/src/cmd/api/testdata/src/pkg/p2/p2.go
new file mode 100644
index 0000000..6b107b5
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p2/p2.go
@@ -0,0 +1,9 @@
+package p2
+
+type Twoer interface {
+ PackageTwoMeth()
+}
+
+func F() string {}
+func G() Twoer {}
+func NewError(s string) error {}
diff --git a/src/cmd/api/testdata/src/pkg/p3/golden.txt b/src/cmd/api/testdata/src/pkg/p3/golden.txt
new file mode 100644
index 0000000..a7dcccd
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p3/golden.txt
@@ -0,0 +1,3 @@
+pkg p3, func BadHop(int, int, int) (bool, bool, *ThirdBase, *ThirdBase, error)
+pkg p3, method (*ThirdBase) GoodPlayer() (int, int, int)
+pkg p3, type ThirdBase struct
diff --git a/src/cmd/api/testdata/src/pkg/p3/p3.go b/src/cmd/api/testdata/src/pkg/p3/p3.go
new file mode 100644
index 0000000..1b2b1a4
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p3/p3.go
@@ -0,0 +1,6 @@
+package p3
+
+type ThirdBase struct{}
+
+func (tb *ThirdBase) GoodPlayer() (i, j, k int)
+func BadHop(i, j, k int) (l, m bool, n, o *ThirdBase, err error)
diff --git a/src/cmd/api/testdata/src/pkg/p4/golden.txt b/src/cmd/api/testdata/src/pkg/p4/golden.txt
new file mode 100644
index 0000000..7997ab4
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p4/golden.txt
@@ -0,0 +1,5 @@
+pkg p4, func NewPair[$0 interface{ M }, $1 interface{ ~int }]($0, $1) Pair
+pkg p4, method (Pair[$0, $1]) Second() $1
+pkg p4, method (Pair[$0, $1]) First() $0
+pkg p4, type Pair[$0 interface{ M }, $1 interface{ ~int }] struct
+pkg p4, func Clone[$0 interface{ ~[]$1 }, $1 interface{}]($0) $0
diff --git a/src/cmd/api/testdata/src/pkg/p4/p4.go b/src/cmd/api/testdata/src/pkg/p4/p4.go
new file mode 100644
index 0000000..1f90e77
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p4/p4.go
@@ -0,0 +1,26 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p4
+
+type Pair[T1 interface{ M() }, T2 ~int] struct {
+ f1 T1
+ f2 T2
+}
+
+func NewPair[T1 interface{ M() }, T2 ~int](v1 T1, v2 T2) Pair[T1, T2] {
+ return Pair[T1, T2]{f1: v1, f2: v2}
+}
+
+func (p Pair[X1, _]) First() X1 {
+ return p.f1
+}
+
+func (p Pair[_, X2]) Second() X2 {
+ return p.f2
+}
+
+func Clone[S ~[]T, T any](s S) S {
+ return append(S(nil), s...)
+}