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