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
8package main
9
10import (
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
35const verbose = false
36
37func 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.
50var 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
87func 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
98var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
99
100var exitCode = 0
101
102func 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.
189func (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
205func 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
213var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
214
215func 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.
224func portRemoved(feature string) bool {
225	return strings.Contains(feature, "(darwin-386)") ||
226		strings.Contains(feature, "(darwin-386-cgo)")
227}
228
229func 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.
282var aliasReplacer = strings.NewReplacer(
283	"os.FileInfo", "fs.FileInfo",
284	"os.FileMode", "fs.FileMode",
285	"os.PathError", "fs.PathError",
286)
287
288func 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
350var fset = token.NewFileSet()
351
352type 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
366func 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
377func (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
385var parsedFileCache = make(map[string]*ast.File)
386
387func (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.
403const usePkgCache = true
404
405var (
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.
416func 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
445type 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
451var 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.
457var listSem = make(chan semToken, 2)
458
459type 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.
476func (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.
555func 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
571type 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.
578var importing apiPackage
579
580// Import implements types.Importer.
581func (w *Walker) Import(name string) (*types.Package, error) {
582	return w.ImportFrom(name, "", 0)
583}
584
585// ImportFrom implements types.ImporterFrom.
586func (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
594func (w *Walker) import_(name string) (*apiPackage, error) {
595	return w.importFrom(name, "", 0)
596}
597
598func (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)
701func (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
714func 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.
726func (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
754func (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.Alias:
847		w.writeType(buf, types.Unalias(typ))
848
849	case *types.Named:
850		obj := typ.Obj()
851		pkg := obj.Pkg()
852		if pkg != nil && pkg != w.current.Package {
853			buf.WriteString(pkg.Name())
854			buf.WriteByte('.')
855		}
856		buf.WriteString(typ.Obj().Name())
857		if targs := typ.TypeArgs(); targs.Len() > 0 {
858			buf.WriteByte('[')
859			for i := 0; i < targs.Len(); i++ {
860				if i > 0 {
861					buf.WriteString(", ")
862				}
863				w.writeType(buf, targs.At(i))
864			}
865			buf.WriteByte(']')
866		}
867
868	case *types.TypeParam:
869		// Type parameter names may change, so use a placeholder instead.
870		fmt.Fprintf(buf, "$%d", typ.Index())
871
872	default:
873		panic(fmt.Sprintf("unknown type %T", typ))
874	}
875}
876
877func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
878	if tparams := sig.TypeParams(); tparams != nil {
879		w.writeTypeParams(buf, tparams, true)
880	}
881	w.writeParams(buf, sig.Params(), sig.Variadic())
882	switch res := sig.Results(); res.Len() {
883	case 0:
884		// nothing to do
885	case 1:
886		buf.WriteByte(' ')
887		w.writeType(buf, res.At(0).Type())
888	default:
889		buf.WriteByte(' ')
890		w.writeParams(buf, res, false)
891	}
892}
893
894func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
895	buf.WriteByte('[')
896	c := tparams.Len()
897	for i := 0; i < c; i++ {
898		if i > 0 {
899			buf.WriteString(", ")
900		}
901		tp := tparams.At(i)
902		w.writeType(buf, tp)
903		if withConstraints {
904			buf.WriteByte(' ')
905			w.writeType(buf, tp.Constraint())
906		}
907	}
908	buf.WriteByte(']')
909}
910
911func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
912	buf.WriteByte('(')
913	for i, n := 0, t.Len(); i < n; i++ {
914		if i > 0 {
915			buf.WriteString(", ")
916		}
917		typ := t.At(i).Type()
918		if variadic && i+1 == n {
919			buf.WriteString("...")
920			typ = typ.(*types.Slice).Elem()
921		}
922		w.writeType(buf, typ)
923	}
924	buf.WriteByte(')')
925}
926
927func (w *Walker) typeString(typ types.Type) string {
928	var buf bytes.Buffer
929	w.writeType(&buf, typ)
930	return buf.String()
931}
932
933func (w *Walker) signatureString(sig *types.Signature) string {
934	var buf bytes.Buffer
935	w.writeSignature(&buf, sig)
936	return buf.String()
937}
938
939func (w *Walker) emitObj(obj types.Object) {
940	switch obj := obj.(type) {
941	case *types.Const:
942		if w.isDeprecated(obj) {
943			w.emitf("const %s //deprecated", obj.Name())
944		}
945		w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
946		x := obj.Val()
947		short := x.String()
948		exact := x.ExactString()
949		if short == exact {
950			w.emitf("const %s = %s", obj.Name(), short)
951		} else {
952			w.emitf("const %s = %s  // %s", obj.Name(), short, exact)
953		}
954	case *types.Var:
955		if w.isDeprecated(obj) {
956			w.emitf("var %s //deprecated", obj.Name())
957		}
958		w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
959	case *types.TypeName:
960		w.emitType(obj)
961	case *types.Func:
962		w.emitFunc(obj)
963	default:
964		panic("unknown object: " + obj.String())
965	}
966}
967
968func (w *Walker) emitType(obj *types.TypeName) {
969	name := obj.Name()
970	if w.isDeprecated(obj) {
971		w.emitf("type %s //deprecated", name)
972	}
973	typ := obj.Type()
974	if obj.IsAlias() {
975		w.emitf("type %s = %s", name, w.typeString(typ))
976		return
977	}
978	if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
979		var buf bytes.Buffer
980		buf.WriteString(name)
981		w.writeTypeParams(&buf, tparams, true)
982		name = buf.String()
983	}
984	switch typ := typ.Underlying().(type) {
985	case *types.Struct:
986		w.emitStructType(name, typ)
987	case *types.Interface:
988		w.emitIfaceType(name, typ)
989		return // methods are handled by emitIfaceType
990	default:
991		w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
992	}
993
994	// emit methods with value receiver
995	var methodNames map[string]bool
996	vset := types.NewMethodSet(typ)
997	for i, n := 0, vset.Len(); i < n; i++ {
998		m := vset.At(i)
999		if m.Obj().Exported() {
1000			w.emitMethod(m)
1001			if methodNames == nil {
1002				methodNames = make(map[string]bool)
1003			}
1004			methodNames[m.Obj().Name()] = true
1005		}
1006	}
1007
1008	// emit methods with pointer receiver; exclude
1009	// methods that we have emitted already
1010	// (the method set of *T includes the methods of T)
1011	pset := types.NewMethodSet(types.NewPointer(typ))
1012	for i, n := 0, pset.Len(); i < n; i++ {
1013		m := pset.At(i)
1014		if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
1015			w.emitMethod(m)
1016		}
1017	}
1018}
1019
1020func (w *Walker) emitStructType(name string, typ *types.Struct) {
1021	typeStruct := fmt.Sprintf("type %s struct", name)
1022	w.emitf(typeStruct)
1023	defer w.pushScope(typeStruct)()
1024
1025	for i := 0; i < typ.NumFields(); i++ {
1026		f := typ.Field(i)
1027		if !f.Exported() {
1028			continue
1029		}
1030		typ := f.Type()
1031		if f.Anonymous() {
1032			if w.isDeprecated(f) {
1033				w.emitf("embedded %s //deprecated", w.typeString(typ))
1034			}
1035			w.emitf("embedded %s", w.typeString(typ))
1036			continue
1037		}
1038		if w.isDeprecated(f) {
1039			w.emitf("%s //deprecated", f.Name())
1040		}
1041		w.emitf("%s %s", f.Name(), w.typeString(typ))
1042	}
1043}
1044
1045func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
1046	pop := w.pushScope("type " + name + " interface")
1047
1048	var methodNames []string
1049	complete := true
1050	mset := types.NewMethodSet(typ)
1051	for i, n := 0, mset.Len(); i < n; i++ {
1052		m := mset.At(i).Obj().(*types.Func)
1053		if !m.Exported() {
1054			complete = false
1055			continue
1056		}
1057		methodNames = append(methodNames, m.Name())
1058		if w.isDeprecated(m) {
1059			w.emitf("%s //deprecated", m.Name())
1060		}
1061		w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
1062	}
1063
1064	if !complete {
1065		// The method set has unexported methods, so all the
1066		// implementations are provided by the same package,
1067		// so the method set can be extended. Instead of recording
1068		// the full set of names (below), record only that there were
1069		// unexported methods. (If the interface shrinks, we will notice
1070		// because a method signature emitted during the last loop
1071		// will disappear.)
1072		w.emitf("unexported methods")
1073	}
1074
1075	pop()
1076
1077	if !complete {
1078		return
1079	}
1080
1081	if len(methodNames) == 0 {
1082		w.emitf("type %s interface {}", name)
1083		return
1084	}
1085
1086	sort.Strings(methodNames)
1087	w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
1088}
1089
1090func (w *Walker) emitFunc(f *types.Func) {
1091	sig := f.Type().(*types.Signature)
1092	if sig.Recv() != nil {
1093		panic("method considered a regular function: " + f.String())
1094	}
1095	if w.isDeprecated(f) {
1096		w.emitf("func %s //deprecated", f.Name())
1097	}
1098	w.emitf("func %s%s", f.Name(), w.signatureString(sig))
1099}
1100
1101func (w *Walker) emitMethod(m *types.Selection) {
1102	sig := m.Type().(*types.Signature)
1103	recv := sig.Recv().Type()
1104	// report exported methods with unexported receiver base type
1105	if true {
1106		base := recv
1107		if p, _ := recv.(*types.Pointer); p != nil {
1108			base = p.Elem()
1109		}
1110		if obj := base.(*types.Named).Obj(); !obj.Exported() {
1111			log.Fatalf("exported method with unexported receiver base type: %s", m)
1112		}
1113	}
1114	tps := ""
1115	if rtp := sig.RecvTypeParams(); rtp != nil {
1116		var buf bytes.Buffer
1117		w.writeTypeParams(&buf, rtp, false)
1118		tps = buf.String()
1119	}
1120	if w.isDeprecated(m.Obj()) {
1121		w.emitf("method (%s%s) %s //deprecated", w.typeString(recv), tps, m.Obj().Name())
1122	}
1123	w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
1124}
1125
1126func (w *Walker) emitf(format string, args ...any) {
1127	f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
1128	if strings.Contains(f, "\n") {
1129		panic("feature contains newlines: " + f)
1130	}
1131
1132	if _, dup := w.features[f]; dup {
1133		panic("duplicate feature inserted: " + f)
1134	}
1135	w.features[f] = true
1136
1137	if verbose {
1138		log.Printf("feature: %s", f)
1139	}
1140}
1141
1142func needApproval(filename string) bool {
1143	name := filepath.Base(filename)
1144	if name == "go1.txt" {
1145		return false
1146	}
1147	minor := strings.TrimSuffix(strings.TrimPrefix(name, "go1."), ".txt")
1148	n, err := strconv.Atoi(minor)
1149	if err != nil {
1150		log.Fatalf("unexpected api file: %v", name)
1151	}
1152	return n >= 19 // started tracking approvals in Go 1.19
1153}
1154
1155func (w *Walker) collectDeprecated() {
1156	isDeprecated := func(doc *ast.CommentGroup) bool {
1157		if doc != nil {
1158			for _, c := range doc.List {
1159				if strings.HasPrefix(c.Text, "// Deprecated:") {
1160					return true
1161				}
1162			}
1163		}
1164		return false
1165	}
1166
1167	w.deprecated = make(map[token.Pos]bool)
1168	mark := func(id *ast.Ident) {
1169		if id != nil {
1170			w.deprecated[id.Pos()] = true
1171		}
1172	}
1173	for _, file := range w.current.Files {
1174		ast.Inspect(file, func(n ast.Node) bool {
1175			switch n := n.(type) {
1176			case *ast.File:
1177				if isDeprecated(n.Doc) {
1178					mark(n.Name)
1179				}
1180				return true
1181			case *ast.GenDecl:
1182				if isDeprecated(n.Doc) {
1183					for _, spec := range n.Specs {
1184						switch spec := spec.(type) {
1185						case *ast.ValueSpec:
1186							for _, id := range spec.Names {
1187								mark(id)
1188							}
1189						case *ast.TypeSpec:
1190							mark(spec.Name)
1191						}
1192					}
1193				}
1194				return true // look at specs
1195			case *ast.FuncDecl:
1196				if isDeprecated(n.Doc) {
1197					mark(n.Name)
1198				}
1199				return false
1200			case *ast.TypeSpec:
1201				if isDeprecated(n.Doc) {
1202					mark(n.Name)
1203				}
1204				return true // recurse into struct or interface type
1205			case *ast.StructType:
1206				return true // recurse into fields
1207			case *ast.InterfaceType:
1208				return true // recurse into methods
1209			case *ast.FieldList:
1210				return true // recurse into fields
1211			case *ast.ValueSpec:
1212				if isDeprecated(n.Doc) {
1213					for _, id := range n.Names {
1214						mark(id)
1215					}
1216				}
1217				return false
1218			case *ast.Field:
1219				if isDeprecated(n.Doc) {
1220					for _, id := range n.Names {
1221						mark(id)
1222					}
1223					if len(n.Names) == 0 {
1224						// embedded field T or *T?
1225						typ := n.Type
1226						if ptr, ok := typ.(*ast.StarExpr); ok {
1227							typ = ptr.X
1228						}
1229						if id, ok := typ.(*ast.Ident); ok {
1230							mark(id)
1231						}
1232					}
1233				}
1234				return false
1235			default:
1236				return false
1237			}
1238		})
1239	}
1240}
1241
1242func (w *Walker) isDeprecated(obj types.Object) bool {
1243	return w.deprecated[obj.Pos()]
1244}
1245