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.
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.
39 if runtime.GOOS == "windows" {
42 path := filepath.Join(testenv.GOROOT(nil), "bin", "go"+exeSuffix)
43 if _, err := os.Stat(path); err == nil {
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"},
87 func contextName(c *build.Context) string {
88 s := c.GOOS + "-" + c.GOARCH
93 s += fmt.Sprintf(" [%s]", c.Dir)
98 var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
102 func Check(t *testing.T) {
103 checkFiles, err := filepath.Glob(filepath.Join(testenv.GOROOT(t), "api/go1*.txt"))
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"))
117 for _, c := range contexts {
118 c.Compiler = build.Default.Compiler
121 walkers := make([]*Walker, len(contexts))
122 var wg sync.WaitGroup
123 for i, context := range contexts {
124 i, context := i, context
128 walkers[i] = NewWalker(context, filepath.Join(testenv.GOROOT(t), "src"))
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 {
141 log.Fatalf("Import(%q): %v", name, err)
146 ctxName := contextName(w.context)
147 for _, f := range w.Features() {
148 if featureCtx[f] == nil {
149 featureCtx[f] = make(map[string]bool)
151 featureCtx[f][ctxName] = true
155 var features []string
156 for f, cmap := range featureCtx {
157 if len(cmap) == len(contexts) {
158 features = append(features, f)
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)
168 bw := bufio.NewWriter(os.Stdout)
171 var required []string
172 for _, file := range checkFiles {
173 required = append(required, fileFeatures(file, needApproval(file))...)
175 for _, file := range nextFiles {
176 required = append(required, fileFeatures(file, true)...)
178 exception := fileFeatures(filepath.Join(testenv.GOROOT(t), "api/except.txt"), false)
181 t.Errorf("API database problems found")
183 if !compareAPI(bw, features, required, exception) {
184 t.Errorf("API differences found")
188 // export emits the exported package features.
189 func (w *Walker) export(pkg *apiPackage) {
193 pop := w.pushScope("pkg " + pkg.Path())
195 w.collectDeprecated()
197 for _, name := range scope.Names() {
198 if token.IsExported(name) {
199 w.emitObj(scope.Lookup(name))
205 func set(items []string) map[string]bool {
206 s := make(map[string]bool)
207 for _, v := range items {
213 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
215 func featureWithoutContext(f string) string {
216 if !strings.Contains(f, "(") {
219 return spaceParensRx.ReplaceAllString(f, "")
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)")
229 func compareAPI(w io.Writer, features, required, exception []string) (ok bool) {
232 featureSet := set(features)
233 exceptionSet := set(exception)
235 sort.Strings(features)
236 sort.Strings(required)
238 take := func(sl *[]string) string {
244 for len(features) > 0 || len(required) > 0 {
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
255 } else if portRemoved(feature) {
257 } else if featureSet[featureWithoutContext(feature)] {
260 fmt.Fprintf(w, "-%s\n", feature)
261 ok = false // broke compatibility
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/*
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",
288 func fileFeatures(filename string, needApproval bool) []string {
289 bs, err := os.ReadFile(filename)
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)
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)
312 log.Printf("%s: empty file", filename)
314 } else if s[len(s)-1] != '\n' {
315 log.Printf("%s: missing final newline", filename)
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, "#") {
327 feature, approval, ok := strings.Cut(line, "#")
329 log.Printf("%s:%d: missing proposal approval\n", filename, i+1)
332 _, err := strconv.Atoi(approval)
334 log.Printf("%s:%d: malformed proposal approval #%s\n", filename, i+1, approval)
338 line = strings.TrimSpace(feature)
340 if strings.Contains(line, " #") {
341 log.Printf("%s:%d: unexpected approval\n", filename, i+1)
345 nonblank = append(nonblank, line)
350 var fset = token.NewFileSet()
353 context *build.Context
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
366 func NewWalker(context *build.Context, root string) *Walker {
370 features: map[string]bool{},
371 imported: map[string]*apiPackage{"unsafe": &apiPackage{Package: types.Unsafe}},
377 func (w *Walker) Features() (fs []string) {
378 for f := range w.features {
385 var parsedFileCache = make(map[string]*ast.File)
387 func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
388 filename := filepath.Join(dir, file)
389 if f := parsedFileCache[filename]; f != nil {
393 f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
397 parsedFileCache[filename] = f
402 // Disable before debugging non-obvious errors from the type-checker.
403 const usePkgCache = true
406 pkgCache = map[string]*apiPackage{} // map tagKey to package
407 pkgTags = map[string][]string{} // map import dir to list of relevant tags
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{
419 context.GOARCH: true,
421 if context.CgoEnabled {
424 for _, tag := range context.BuildTags {
427 // TODO: ReleaseTags (need to load default)
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
433 tags = append(tags, context.GOOS, context.GOARCH)
436 for _, tag := range tags {
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
451 var listCache sync.Map // map[string]listImports, keyed by contextName
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)
459 type semToken struct{}
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.
464 // The source import path and expanded import path are identical except for vendored packages.
465 // For example, on return:
467 // w.importMap["math"] = "math"
468 // w.importDir["math"] = "<goroot>/src/math"
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"
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
481 name := contextName(w.context)
483 imports, ok := listCache.Load(name)
485 listSem <- semToken{}
486 defer func() { <-listSem }()
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
493 cmd.Stderr = os.Stderr
494 out, err := cmd.Output()
496 log.Fatalf("loading imports: %v\n%s", err, out)
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))
505 ImportPath, Dir string
506 ImportMap map[string]string
509 err := dec.Decode(&pkg)
514 log.Fatalf("go list: invalid output: %v", err)
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
526 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
527 stdPackages = append(stdPackages, ip)
529 importDir[pkg.ImportPath] = pkg.Dir
530 if len(pkg.ImportMap) > 0 {
531 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
533 for k, v := range pkg.ImportMap {
534 importMap[pkg.Dir][k] = v
538 sort.Strings(stdPackages)
539 imports = listImports{
540 stdPackages: stdPackages,
541 importMap: importMap,
542 importDir: importDir,
544 imports, _ = listCache.LoadOrStore(name, imports)
547 li := imports.(listImports)
548 w.stdPackages = li.stdPackages
549 w.importDir = li.importDir
550 w.importMap = li.importMap
553 // listEnv returns the process environment to use when invoking 'go list' for
554 // the given context.
555 func listEnv(c *build.Context) []string {
560 environ := append(os.Environ(),
564 environ = append(environ, "CGO_ENABLED=1")
566 environ = append(environ, "CGO_ENABLED=0")
571 type apiPackage struct {
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
580 // Import implements types.Importer.
581 func (w *Walker) Import(name string) (*types.Package, error) {
582 return w.ImportFrom(name, "", 0)
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)
591 return pkg.Package, nil
594 func (w *Walker) import_(name string) (*apiPackage, error) {
595 return w.importFrom(name, "", 0)
598 func (w *Walker) importFrom(fromPath, fromDir string, mode types.ImportMode) (*apiPackage, error) {
600 if canonical, ok := w.importMap[fromDir][fromPath]; ok {
604 pkg := w.imported[name]
606 if pkg == &importing {
607 log.Fatalf("cycle importing package %q", name)
611 w.imported[name] = &importing
613 // Determine package files.
614 dir := w.importDir[name]
616 dir = filepath.Join(w.root, filepath.FromSlash(name))
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)
624 context = &build.Default
628 // If we've already done an import with the same set
629 // of relevant tags, reuse the result.
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
641 info, err := context.ImportDir(dir, 0)
643 if _, nogo := err.(*build.NoGoError); nogo {
646 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
649 // Save tags list first time we see a directory.
651 if _, ok := pkgTags[dir]; !ok {
652 pkgTags[dir] = info.AllTags
653 key = tagKey(dir, context, info.AllTags)
657 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
659 // Parse package files.
660 var files []*ast.File
661 for _, file := range filenames {
662 f, err := w.parseFile(dir, file)
664 log.Fatalf("error parsing package %s: %s", name, err)
666 files = append(files, f)
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)
674 conf := types.Config{
675 IgnoreFuncBodies: true,
680 tpkg, err := conf.Check(name, fset, files, nil)
682 ctxt := "<no context>"
683 if w.context != nil {
684 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
686 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
688 pkg = &apiPackage{tpkg, files}
694 w.imported[name] = pkg
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)
704 if len(w.scope) == 0 {
705 log.Fatalf("attempt to leave scope %q with empty scope list", name)
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)
710 w.scope = w.scope[:len(w.scope)-1]
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()
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)...)
737 for i := 0; i < nu; i++ {
739 buf.WriteString(" | ")
745 w.writeType(&buf, term.Type())
747 list = append(list, buf.String())
754 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
755 switch typ := typ.(type) {
759 case types.UnsafePointer:
761 case types.UntypedBool:
763 case types.UntypedInt:
765 case types.UntypedRune:
766 // "ideal-char" for compatibility with old tool
767 // TODO(gri) change to "ideal-rune"
769 case types.UntypedFloat:
771 case types.UntypedComplex:
773 case types.UntypedString:
775 case types.UntypedNil:
776 panic("should never see untyped nil type")
788 fmt.Fprintf(buf, "[%d]", typ.Len())
789 w.writeType(buf, typ.Elem())
792 buf.WriteString("[]")
793 w.writeType(buf, typ.Elem())
796 buf.WriteString("struct")
800 w.writeType(buf, typ.Elem())
803 panic("should never see a tuple type")
805 case *types.Signature:
806 buf.WriteString("func")
807 w.writeSignature(buf, typ)
809 case *types.Interface:
810 buf.WriteString("interface{")
811 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
814 if typ.NumMethods() > 0 {
815 buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
817 if typ.NumEmbeddeds() > 0 {
818 buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", "))
820 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
826 buf.WriteString("map[")
827 w.writeType(buf, typ.Key())
829 w.writeType(buf, typ.Elem())
844 w.writeType(buf, typ.Elem())
849 if pkg != nil && pkg != w.current.Package {
850 buf.WriteString(pkg.Name())
853 buf.WriteString(typ.Obj().Name())
855 case *types.TypeParam:
856 // Type parameter names may change, so use a placeholder instead.
857 fmt.Fprintf(buf, "$%d", typ.Index())
860 panic(fmt.Sprintf("unknown type %T", typ))
864 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
865 if tparams := sig.TypeParams(); tparams != nil {
866 w.writeTypeParams(buf, tparams, true)
868 w.writeParams(buf, sig.Params(), sig.Variadic())
869 switch res := sig.Results(); res.Len() {
874 w.writeType(buf, res.At(0).Type())
877 w.writeParams(buf, res, false)
881 func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
884 for i := 0; i < c; i++ {
886 buf.WriteString(", ")
892 w.writeType(buf, tp.Constraint())
898 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
900 for i, n := 0, t.Len(); i < n; i++ {
902 buf.WriteString(", ")
904 typ := t.At(i).Type()
905 if variadic && i+1 == n {
906 buf.WriteString("...")
907 typ = typ.(*types.Slice).Elem()
909 w.writeType(buf, typ)
914 func (w *Walker) typeString(typ types.Type) string {
916 w.writeType(&buf, typ)
920 func (w *Walker) signatureString(sig *types.Signature) string {
922 w.writeSignature(&buf, sig)
926 func (w *Walker) emitObj(obj types.Object) {
927 switch obj := obj.(type) {
929 if w.isDeprecated(obj) {
930 w.emitf("const %s //deprecated", obj.Name())
932 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
935 exact := x.ExactString()
937 w.emitf("const %s = %s", obj.Name(), short)
939 w.emitf("const %s = %s // %s", obj.Name(), short, exact)
942 if w.isDeprecated(obj) {
943 w.emitf("var %s //deprecated", obj.Name())
945 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
946 case *types.TypeName:
951 panic("unknown object: " + obj.String())
955 func (w *Walker) emitType(obj *types.TypeName) {
957 if w.isDeprecated(obj) {
958 w.emitf("type %s //deprecated", name)
960 if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
962 buf.WriteString(name)
963 w.writeTypeParams(&buf, tparams, true)
968 w.emitf("type %s = %s", name, w.typeString(typ))
971 switch typ := typ.Underlying().(type) {
973 w.emitStructType(name, typ)
974 case *types.Interface:
975 w.emitIfaceType(name, typ)
976 return // methods are handled by emitIfaceType
978 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
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++ {
986 if m.Obj().Exported() {
988 if methodNames == nil {
989 methodNames = make(map[string]bool)
991 methodNames[m.Obj().Name()] = true
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++ {
1001 if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
1007 func (w *Walker) emitStructType(name string, typ *types.Struct) {
1008 typeStruct := fmt.Sprintf("type %s struct", name)
1010 defer w.pushScope(typeStruct)()
1012 for i := 0; i < typ.NumFields(); i++ {
1019 if w.isDeprecated(f) {
1020 w.emitf("embedded %s //deprecated", w.typeString(typ))
1022 w.emitf("embedded %s", w.typeString(typ))
1025 if w.isDeprecated(f) {
1026 w.emitf("%s //deprecated", f.Name())
1028 w.emitf("%s %s", f.Name(), w.typeString(typ))
1032 func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
1033 pop := w.pushScope("type " + name + " interface")
1035 var methodNames []string
1037 mset := types.NewMethodSet(typ)
1038 for i, n := 0, mset.Len(); i < n; i++ {
1039 m := mset.At(i).Obj().(*types.Func)
1044 methodNames = append(methodNames, m.Name())
1045 if w.isDeprecated(m) {
1046 w.emitf("%s //deprecated", m.Name())
1048 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
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
1059 w.emitf("unexported methods")
1068 if len(methodNames) == 0 {
1069 w.emitf("type %s interface {}", name)
1073 sort.Strings(methodNames)
1074 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
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())
1082 if w.isDeprecated(f) {
1083 w.emitf("func %s //deprecated", f.Name())
1085 w.emitf("func %s%s", f.Name(), w.signatureString(sig))
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
1094 if p, _ := recv.(*types.Pointer); p != nil {
1097 if obj := base.(*types.Named).Obj(); !obj.Exported() {
1098 log.Fatalf("exported method with unexported receiver base type: %s", m)
1102 if rtp := sig.RecvTypeParams(); rtp != nil {
1103 var buf bytes.Buffer
1104 w.writeTypeParams(&buf, rtp, false)
1107 if w.isDeprecated(m.Obj()) {
1108 w.emitf("method (%s%s) %s //deprecated", w.typeString(recv), tps, m.Obj().Name())
1110 w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
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)
1119 if _, dup := w.features[f]; dup {
1120 panic("duplicate feature inserted: " + f)
1122 w.features[f] = true
1125 log.Printf("feature: %s", f)
1129 func needApproval(filename string) bool {
1130 name := filepath.Base(filename)
1131 if name == "go1.txt" {
1134 minor := strings.TrimSuffix(strings.TrimPrefix(name, "go1."), ".txt")
1135 n, err := strconv.Atoi(minor)
1137 log.Fatalf("unexpected api file: %v", name)
1139 return n >= 19 // started tracking approvals in Go 1.19
1142 func (w *Walker) collectDeprecated() {
1143 isDeprecated := func(doc *ast.CommentGroup) bool {
1145 for _, c := range doc.List {
1146 if strings.HasPrefix(c.Text, "// Deprecated:") {
1154 w.deprecated = make(map[token.Pos]bool)
1155 mark := func(id *ast.Ident) {
1157 w.deprecated[id.Pos()] = true
1160 for _, file := range w.current.Files {
1161 ast.Inspect(file, func(n ast.Node) bool {
1162 switch n := n.(type) {
1164 if isDeprecated(n.Doc) {
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 {
1181 return true // look at specs
1183 if isDeprecated(n.Doc) {
1188 if isDeprecated(n.Doc) {
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 {
1206 if isDeprecated(n.Doc) {
1207 for _, id := range n.Names {
1210 if len(n.Names) == 0 {
1211 // embedded field T or *T?
1213 if ptr, ok := typ.(*ast.StarExpr); ok {
1216 if id, ok := typ.(*ast.Ident); ok {
1229 func (w *Walker) isDeprecated(obj types.Object) bool {
1230 return w.deprecated[obj.Pos()]