Source file
src/cmd/api/main_test.go
1
2
3
4
5
6
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 "slices"
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
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 wg.Add(1)
125 go func() {
126 defer wg.Done()
127 walkers[i] = NewWalker(context, filepath.Join(testenv.GOROOT(t), "src"))
128 }()
129 }
130 wg.Wait()
131
132 var featureCtx = make(map[string]map[string]bool)
133 for _, w := range walkers {
134 for _, name := range w.stdPackages {
135 pkg, err := w.import_(name)
136 if _, nogo := err.(*build.NoGoError); nogo {
137 continue
138 }
139 if err != nil {
140 log.Fatalf("Import(%q): %v", name, err)
141 }
142 w.export(pkg)
143 }
144
145 ctxName := contextName(w.context)
146 for _, f := range w.Features() {
147 if featureCtx[f] == nil {
148 featureCtx[f] = make(map[string]bool)
149 }
150 featureCtx[f][ctxName] = true
151 }
152 }
153
154 var features []string
155 for f, cmap := range featureCtx {
156 if len(cmap) == len(contexts) {
157 features = append(features, f)
158 continue
159 }
160 comma := strings.Index(f, ",")
161 for cname := range cmap {
162 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
163 features = append(features, f2)
164 }
165 }
166
167 bw := bufio.NewWriter(os.Stdout)
168 defer bw.Flush()
169
170 var required []string
171 for _, file := range checkFiles {
172 required = append(required, fileFeatures(file, needApproval(file))...)
173 }
174 for _, file := range nextFiles {
175 required = append(required, fileFeatures(file, true)...)
176 }
177 exception := fileFeatures(filepath.Join(testenv.GOROOT(t), "api/except.txt"), false)
178
179 if exitCode == 1 {
180 t.Errorf("API database problems found")
181 }
182 if !compareAPI(bw, features, required, exception) {
183 t.Errorf("API differences found")
184 }
185 }
186
187
188 func (w *Walker) export(pkg *apiPackage) {
189 if verbose {
190 log.Println(pkg)
191 }
192 pop := w.pushScope("pkg " + pkg.Path())
193 w.current = pkg
194 w.collectDeprecated()
195 scope := pkg.Scope()
196 for _, name := range scope.Names() {
197 if token.IsExported(name) {
198 w.emitObj(scope.Lookup(name))
199 }
200 }
201 pop()
202 }
203
204 func set(items []string) map[string]bool {
205 s := make(map[string]bool)
206 for _, v := range items {
207 s[v] = true
208 }
209 return s
210 }
211
212 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
213
214 func featureWithoutContext(f string) string {
215 if !strings.Contains(f, "(") {
216 return f
217 }
218 return spaceParensRx.ReplaceAllString(f, "")
219 }
220
221
222
223 func portRemoved(feature string) bool {
224 return strings.Contains(feature, "(darwin-386)") ||
225 strings.Contains(feature, "(darwin-386-cgo)")
226 }
227
228 func compareAPI(w io.Writer, features, required, exception []string) (ok bool) {
229 ok = true
230
231 featureSet := set(features)
232 exceptionSet := set(exception)
233
234 slices.Sort(features)
235 slices.Sort(required)
236
237 take := func(sl *[]string) string {
238 s := (*sl)[0]
239 *sl = (*sl)[1:]
240 return s
241 }
242
243 for len(features) > 0 || len(required) > 0 {
244 switch {
245 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
246 feature := take(&required)
247 if exceptionSet[feature] {
248
249
250
251
252
253
254 } else if portRemoved(feature) {
255
256 } else if featureSet[featureWithoutContext(feature)] {
257
258 } else {
259 fmt.Fprintf(w, "-%s\n", feature)
260 ok = false
261 }
262 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
263 newFeature := take(&features)
264 fmt.Fprintf(w, "+%s\n", newFeature)
265 ok = false
266 default:
267 take(&required)
268 take(&features)
269 }
270 }
271
272 return ok
273 }
274
275
276
277
278
279
280
281 var aliasReplacer = strings.NewReplacer(
282 "os.FileInfo", "fs.FileInfo",
283 "os.FileMode", "fs.FileMode",
284 "os.PathError", "fs.PathError",
285 )
286
287 func fileFeatures(filename string, needApproval bool) []string {
288 bs, err := os.ReadFile(filename)
289 if err != nil {
290 log.Fatal(err)
291 }
292 s := string(bs)
293
294
295
296
297
298
299 if strings.Contains(s, "\r") {
300 log.Printf("%s: contains CRLFs", filename)
301 exitCode = 1
302 }
303 if filepath.Base(filename) == "go1.4.txt" {
304
305
306 } else if strings.HasPrefix(s, "\n") || strings.Contains(s, "\n\n") {
307 log.Printf("%s: contains a blank line", filename)
308 exitCode = 1
309 }
310 if s == "" {
311 log.Printf("%s: empty file", filename)
312 exitCode = 1
313 } else if s[len(s)-1] != '\n' {
314 log.Printf("%s: missing final newline", filename)
315 exitCode = 1
316 }
317 s = aliasReplacer.Replace(s)
318 lines := strings.Split(s, "\n")
319 var nonblank []string
320 for i, line := range lines {
321 line = strings.TrimSpace(line)
322 if line == "" || strings.HasPrefix(line, "#") {
323 continue
324 }
325 if needApproval {
326 feature, approval, ok := strings.Cut(line, "#")
327 if !ok {
328 log.Printf("%s:%d: missing proposal approval\n", filename, i+1)
329 exitCode = 1
330 } else {
331 _, err := strconv.Atoi(approval)
332 if err != nil {
333 log.Printf("%s:%d: malformed proposal approval #%s\n", filename, i+1, approval)
334 exitCode = 1
335 }
336 }
337 line = strings.TrimSpace(feature)
338 } else {
339 if strings.Contains(line, " #") {
340 log.Printf("%s:%d: unexpected approval\n", filename, i+1)
341 exitCode = 1
342 }
343 }
344 nonblank = append(nonblank, line)
345 }
346 return nonblank
347 }
348
349 var fset = token.NewFileSet()
350
351 type Walker struct {
352 context *build.Context
353 root string
354 scope []string
355 current *apiPackage
356 deprecated map[token.Pos]bool
357 features map[string]bool
358 imported map[string]*apiPackage
359 stdPackages []string
360 importMap map[string]map[string]string
361 importDir map[string]string
362
363 }
364
365 func NewWalker(context *build.Context, root string) *Walker {
366 w := &Walker{
367 context: context,
368 root: root,
369 features: map[string]bool{},
370 imported: map[string]*apiPackage{"unsafe": &apiPackage{Package: types.Unsafe}},
371 }
372 w.loadImports()
373 return w
374 }
375
376 func (w *Walker) Features() (fs []string) {
377 for f := range w.features {
378 fs = append(fs, f)
379 }
380 slices.Sort(fs)
381 return
382 }
383
384 var parsedFileCache = make(map[string]*ast.File)
385
386 func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
387 filename := filepath.Join(dir, file)
388 if f := parsedFileCache[filename]; f != nil {
389 return f, nil
390 }
391
392 f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
393 if err != nil {
394 return nil, err
395 }
396 parsedFileCache[filename] = f
397
398 return f, nil
399 }
400
401
402 const usePkgCache = true
403
404 var (
405 pkgCache = map[string]*apiPackage{}
406 pkgTags = map[string][]string{}
407 )
408
409
410
411
412
413
414
415 func tagKey(dir string, context *build.Context, tags []string) string {
416 ctags := map[string]bool{
417 context.GOOS: true,
418 context.GOARCH: true,
419 }
420 if context.CgoEnabled {
421 ctags["cgo"] = true
422 }
423 for _, tag := range context.BuildTags {
424 ctags[tag] = true
425 }
426
427 key := dir
428
429
430
431
432 tags = append(tags, context.GOOS, context.GOARCH)
433 slices.Sort(tags)
434
435 for _, tag := range tags {
436 if ctags[tag] {
437 key += "," + tag
438 ctags[tag] = false
439 }
440 }
441 return key
442 }
443
444 type listImports struct {
445 stdPackages []string
446 importDir map[string]string
447 importMap map[string]map[string]string
448 }
449
450 var listCache sync.Map
451
452
453
454
455
456 var listSem = make(chan semToken, 2)
457
458 type semToken struct{}
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 func (w *Walker) loadImports() {
476 if w.context == nil {
477 return
478 }
479
480 name := contextName(w.context)
481
482 imports, ok := listCache.Load(name)
483 if !ok {
484 listSem <- semToken{}
485 defer func() { <-listSem }()
486
487 cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
488 cmd.Env = listEnv(w.context)
489 if w.context.Dir != "" {
490 cmd.Dir = w.context.Dir
491 }
492 cmd.Stderr = os.Stderr
493 out, err := cmd.Output()
494 if err != nil {
495 log.Fatalf("loading imports: %v\n%s", err, out)
496 }
497
498 var stdPackages []string
499 importMap := make(map[string]map[string]string)
500 importDir := make(map[string]string)
501 dec := json.NewDecoder(bytes.NewReader(out))
502 for {
503 var pkg struct {
504 ImportPath, Dir string
505 ImportMap map[string]string
506 Standard bool
507 }
508 err := dec.Decode(&pkg)
509 if err == io.EOF {
510 break
511 }
512 if err != nil {
513 log.Fatalf("go list: invalid output: %v", err)
514 }
515
516
517
518
519
520
521
522
523
524
525 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
526 stdPackages = append(stdPackages, ip)
527 }
528 importDir[pkg.ImportPath] = pkg.Dir
529 if len(pkg.ImportMap) > 0 {
530 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
531 }
532 for k, v := range pkg.ImportMap {
533 importMap[pkg.Dir][k] = v
534 }
535 }
536
537 slices.Sort(stdPackages)
538 imports = listImports{
539 stdPackages: stdPackages,
540 importMap: importMap,
541 importDir: importDir,
542 }
543 imports, _ = listCache.LoadOrStore(name, imports)
544 }
545
546 li := imports.(listImports)
547 w.stdPackages = li.stdPackages
548 w.importDir = li.importDir
549 w.importMap = li.importMap
550 }
551
552
553
554 func listEnv(c *build.Context) []string {
555 if c == nil {
556 return os.Environ()
557 }
558
559 environ := append(os.Environ(),
560 "GOOS="+c.GOOS,
561 "GOARCH="+c.GOARCH)
562 if c.CgoEnabled {
563 environ = append(environ, "CGO_ENABLED=1")
564 } else {
565 environ = append(environ, "CGO_ENABLED=0")
566 }
567 return environ
568 }
569
570 type apiPackage struct {
571 *types.Package
572 Files []*ast.File
573 }
574
575
576
577 var importing apiPackage
578
579
580 func (w *Walker) Import(name string) (*types.Package, error) {
581 return w.ImportFrom(name, "", 0)
582 }
583
584
585 func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
586 pkg, err := w.importFrom(fromPath, fromDir, mode)
587 if err != nil {
588 return nil, err
589 }
590 return pkg.Package, nil
591 }
592
593 func (w *Walker) import_(name string) (*apiPackage, error) {
594 return w.importFrom(name, "", 0)
595 }
596
597 func (w *Walker) importFrom(fromPath, fromDir string, mode types.ImportMode) (*apiPackage, error) {
598 name := fromPath
599 if canonical, ok := w.importMap[fromDir][fromPath]; ok {
600 name = canonical
601 }
602
603 pkg := w.imported[name]
604 if pkg != nil {
605 if pkg == &importing {
606 log.Fatalf("cycle importing package %q", name)
607 }
608 return pkg, nil
609 }
610 w.imported[name] = &importing
611
612
613 dir := w.importDir[name]
614 if dir == "" {
615 dir = filepath.Join(w.root, filepath.FromSlash(name))
616 }
617 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
618 log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
619 }
620
621 context := w.context
622 if context == nil {
623 context = &build.Default
624 }
625
626
627
628
629 var key string
630 if usePkgCache {
631 if tags, ok := pkgTags[dir]; ok {
632 key = tagKey(dir, context, tags)
633 if pkg := pkgCache[key]; pkg != nil {
634 w.imported[name] = pkg
635 return pkg, nil
636 }
637 }
638 }
639
640 info, err := context.ImportDir(dir, 0)
641 if err != nil {
642 if _, nogo := err.(*build.NoGoError); nogo {
643 return nil, err
644 }
645 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
646 }
647
648
649 if usePkgCache {
650 if _, ok := pkgTags[dir]; !ok {
651 pkgTags[dir] = info.AllTags
652 key = tagKey(dir, context, info.AllTags)
653 }
654 }
655
656 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
657
658
659 var files []*ast.File
660 for _, file := range filenames {
661 f, err := w.parseFile(dir, file)
662 if err != nil {
663 log.Fatalf("error parsing package %s: %s", name, err)
664 }
665 files = append(files, f)
666 }
667
668
669 var sizes types.Sizes
670 if w.context != nil {
671 sizes = types.SizesFor(w.context.Compiler, w.context.GOARCH)
672 }
673 conf := types.Config{
674 IgnoreFuncBodies: true,
675 FakeImportC: true,
676 Importer: w,
677 Sizes: sizes,
678 }
679 tpkg, err := conf.Check(name, fset, files, nil)
680 if err != nil {
681 ctxt := "<no context>"
682 if w.context != nil {
683 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
684 }
685 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
686 }
687 pkg = &apiPackage{tpkg, files}
688
689 if usePkgCache {
690 pkgCache[key] = pkg
691 }
692
693 w.imported[name] = pkg
694 return pkg, nil
695 }
696
697
698
699
700 func (w *Walker) pushScope(name string) (popFunc func()) {
701 w.scope = append(w.scope, name)
702 return func() {
703 if len(w.scope) == 0 {
704 log.Fatalf("attempt to leave scope %q with empty scope list", name)
705 }
706 if w.scope[len(w.scope)-1] != name {
707 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
708 }
709 w.scope = w.scope[:len(w.scope)-1]
710 }
711 }
712
713 func sortedMethodNames(typ *types.Interface) []string {
714 n := typ.NumMethods()
715 list := make([]string, n)
716 for i := range list {
717 list[i] = typ.Method(i).Name()
718 }
719 slices.Sort(list)
720 return list
721 }
722
723
724
725 func (w *Walker) sortedEmbeddeds(typ *types.Interface) []string {
726 n := typ.NumEmbeddeds()
727 list := make([]string, 0, n)
728 for i := 0; i < n; i++ {
729 emb := typ.EmbeddedType(i)
730 switch emb := emb.(type) {
731 case *types.Interface:
732 list = append(list, w.sortedEmbeddeds(emb)...)
733 case *types.Union:
734 var buf bytes.Buffer
735 nu := emb.Len()
736 for i := 0; i < nu; i++ {
737 if i > 0 {
738 buf.WriteString(" | ")
739 }
740 term := emb.Term(i)
741 if term.Tilde() {
742 buf.WriteByte('~')
743 }
744 w.writeType(&buf, term.Type())
745 }
746 list = append(list, buf.String())
747 }
748 }
749 slices.Sort(list)
750 return list
751 }
752
753 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
754 switch typ := typ.(type) {
755 case *types.Basic:
756 s := typ.Name()
757 switch typ.Kind() {
758 case types.UnsafePointer:
759 s = "unsafe.Pointer"
760 case types.UntypedBool:
761 s = "ideal-bool"
762 case types.UntypedInt:
763 s = "ideal-int"
764 case types.UntypedRune:
765
766
767 s = "ideal-char"
768 case types.UntypedFloat:
769 s = "ideal-float"
770 case types.UntypedComplex:
771 s = "ideal-complex"
772 case types.UntypedString:
773 s = "ideal-string"
774 case types.UntypedNil:
775 panic("should never see untyped nil type")
776 default:
777 switch s {
778 case "byte":
779 s = "uint8"
780 case "rune":
781 s = "int32"
782 }
783 }
784 buf.WriteString(s)
785
786 case *types.Array:
787 fmt.Fprintf(buf, "[%d]", typ.Len())
788 w.writeType(buf, typ.Elem())
789
790 case *types.Slice:
791 buf.WriteString("[]")
792 w.writeType(buf, typ.Elem())
793
794 case *types.Struct:
795 buf.WriteString("struct")
796
797 case *types.Pointer:
798 buf.WriteByte('*')
799 w.writeType(buf, typ.Elem())
800
801 case *types.Tuple:
802 panic("should never see a tuple type")
803
804 case *types.Signature:
805 buf.WriteString("func")
806 w.writeSignature(buf, typ)
807
808 case *types.Interface:
809 buf.WriteString("interface{")
810 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
811 buf.WriteByte(' ')
812 }
813 if typ.NumMethods() > 0 {
814 buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
815 }
816 if typ.NumEmbeddeds() > 0 {
817 buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", "))
818 }
819 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
820 buf.WriteByte(' ')
821 }
822 buf.WriteString("}")
823
824 case *types.Map:
825 buf.WriteString("map[")
826 w.writeType(buf, typ.Key())
827 buf.WriteByte(']')
828 w.writeType(buf, typ.Elem())
829
830 case *types.Chan:
831 var s string
832 switch typ.Dir() {
833 case types.SendOnly:
834 s = "chan<- "
835 case types.RecvOnly:
836 s = "<-chan "
837 case types.SendRecv:
838 s = "chan "
839 default:
840 panic("unreachable")
841 }
842 buf.WriteString(s)
843 w.writeType(buf, typ.Elem())
844
845 case *types.Alias:
846 w.writeType(buf, types.Unalias(typ))
847
848 case *types.Named:
849 obj := typ.Obj()
850 pkg := obj.Pkg()
851 if pkg != nil && pkg != w.current.Package {
852 buf.WriteString(pkg.Name())
853 buf.WriteByte('.')
854 }
855 buf.WriteString(typ.Obj().Name())
856 if targs := typ.TypeArgs(); targs.Len() > 0 {
857 buf.WriteByte('[')
858 for i := 0; i < targs.Len(); i++ {
859 if i > 0 {
860 buf.WriteString(", ")
861 }
862 w.writeType(buf, targs.At(i))
863 }
864 buf.WriteByte(']')
865 }
866
867 case *types.TypeParam:
868
869 fmt.Fprintf(buf, "$%d", typ.Index())
870
871 default:
872 panic(fmt.Sprintf("unknown type %T", typ))
873 }
874 }
875
876 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
877 if tparams := sig.TypeParams(); tparams != nil {
878 w.writeTypeParams(buf, tparams, true)
879 }
880 w.writeParams(buf, sig.Params(), sig.Variadic())
881 switch res := sig.Results(); res.Len() {
882 case 0:
883
884 case 1:
885 buf.WriteByte(' ')
886 w.writeType(buf, res.At(0).Type())
887 default:
888 buf.WriteByte(' ')
889 w.writeParams(buf, res, false)
890 }
891 }
892
893 func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
894 buf.WriteByte('[')
895 c := tparams.Len()
896 for i := 0; i < c; i++ {
897 if i > 0 {
898 buf.WriteString(", ")
899 }
900 tp := tparams.At(i)
901 w.writeType(buf, tp)
902 if withConstraints {
903 buf.WriteByte(' ')
904 w.writeType(buf, tp.Constraint())
905 }
906 }
907 buf.WriteByte(']')
908 }
909
910 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
911 buf.WriteByte('(')
912 for i, n := 0, t.Len(); i < n; i++ {
913 if i > 0 {
914 buf.WriteString(", ")
915 }
916 typ := t.At(i).Type()
917 if variadic && i+1 == n {
918 buf.WriteString("...")
919 typ = typ.(*types.Slice).Elem()
920 }
921 w.writeType(buf, typ)
922 }
923 buf.WriteByte(')')
924 }
925
926 func (w *Walker) typeString(typ types.Type) string {
927 var buf bytes.Buffer
928 w.writeType(&buf, typ)
929 return buf.String()
930 }
931
932 func (w *Walker) signatureString(sig *types.Signature) string {
933 var buf bytes.Buffer
934 w.writeSignature(&buf, sig)
935 return buf.String()
936 }
937
938 func (w *Walker) emitObj(obj types.Object) {
939 switch obj := obj.(type) {
940 case *types.Const:
941 if w.isDeprecated(obj) {
942 w.emitf("const %s //deprecated", obj.Name())
943 }
944 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
945 x := obj.Val()
946 short := x.String()
947 exact := x.ExactString()
948 if short == exact {
949 w.emitf("const %s = %s", obj.Name(), short)
950 } else {
951 w.emitf("const %s = %s // %s", obj.Name(), short, exact)
952 }
953 case *types.Var:
954 if w.isDeprecated(obj) {
955 w.emitf("var %s //deprecated", obj.Name())
956 }
957 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
958 case *types.TypeName:
959 w.emitType(obj)
960 case *types.Func:
961 w.emitFunc(obj)
962 default:
963 panic("unknown object: " + obj.String())
964 }
965 }
966
967 func (w *Walker) emitType(obj *types.TypeName) {
968 name := obj.Name()
969 if w.isDeprecated(obj) {
970 w.emitf("type %s //deprecated", name)
971 }
972 typ := obj.Type()
973 if obj.IsAlias() {
974 w.emitf("type %s = %s", name, w.typeString(typ))
975 return
976 }
977 if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
978 var buf bytes.Buffer
979 buf.WriteString(name)
980 w.writeTypeParams(&buf, tparams, true)
981 name = buf.String()
982 }
983 switch typ := typ.Underlying().(type) {
984 case *types.Struct:
985 w.emitStructType(name, typ)
986 case *types.Interface:
987 w.emitIfaceType(name, typ)
988 return
989 default:
990 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
991 }
992
993
994 var methodNames map[string]bool
995 vset := types.NewMethodSet(typ)
996 for i, n := 0, vset.Len(); i < n; i++ {
997 m := vset.At(i)
998 if m.Obj().Exported() {
999 w.emitMethod(m)
1000 if methodNames == nil {
1001 methodNames = make(map[string]bool)
1002 }
1003 methodNames[m.Obj().Name()] = true
1004 }
1005 }
1006
1007
1008
1009
1010 pset := types.NewMethodSet(types.NewPointer(typ))
1011 for i, n := 0, pset.Len(); i < n; i++ {
1012 m := pset.At(i)
1013 if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
1014 w.emitMethod(m)
1015 }
1016 }
1017 }
1018
1019 func (w *Walker) emitStructType(name string, typ *types.Struct) {
1020 typeStruct := fmt.Sprintf("type %s struct", name)
1021 w.emitf("%s", typeStruct)
1022 defer w.pushScope(typeStruct)()
1023
1024 for i := 0; i < typ.NumFields(); i++ {
1025 f := typ.Field(i)
1026 if !f.Exported() {
1027 continue
1028 }
1029 typ := f.Type()
1030 if f.Anonymous() {
1031 if w.isDeprecated(f) {
1032 w.emitf("embedded %s //deprecated", w.typeString(typ))
1033 }
1034 w.emitf("embedded %s", w.typeString(typ))
1035 continue
1036 }
1037 if w.isDeprecated(f) {
1038 w.emitf("%s //deprecated", f.Name())
1039 }
1040 w.emitf("%s %s", f.Name(), w.typeString(typ))
1041 }
1042 }
1043
1044 func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
1045 pop := w.pushScope("type " + name + " interface")
1046
1047 var methodNames []string
1048 complete := true
1049 mset := types.NewMethodSet(typ)
1050 for i, n := 0, mset.Len(); i < n; i++ {
1051 m := mset.At(i).Obj().(*types.Func)
1052 if !m.Exported() {
1053 complete = false
1054 continue
1055 }
1056 methodNames = append(methodNames, m.Name())
1057 if w.isDeprecated(m) {
1058 w.emitf("%s //deprecated", m.Name())
1059 }
1060 w.emitf("%s%s", m.Name(), w.signatureString(m.Signature()))
1061 }
1062
1063 if !complete {
1064
1065
1066
1067
1068
1069
1070
1071 w.emitf("unexported methods")
1072 }
1073
1074 pop()
1075
1076 if !complete {
1077 return
1078 }
1079
1080 if len(methodNames) == 0 {
1081 w.emitf("type %s interface {}", name)
1082 return
1083 }
1084
1085 slices.Sort(methodNames)
1086 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
1087 }
1088
1089 func (w *Walker) emitFunc(f *types.Func) {
1090 sig := f.Signature()
1091 if sig.Recv() != nil {
1092 panic("method considered a regular function: " + f.String())
1093 }
1094 if w.isDeprecated(f) {
1095 w.emitf("func %s //deprecated", f.Name())
1096 }
1097 w.emitf("func %s%s", f.Name(), w.signatureString(sig))
1098 }
1099
1100 func (w *Walker) emitMethod(m *types.Selection) {
1101 sig := m.Type().(*types.Signature)
1102 recv := sig.Recv().Type()
1103
1104 if true {
1105 base := recv
1106 if p, _ := recv.(*types.Pointer); p != nil {
1107 base = p.Elem()
1108 }
1109 if obj := base.(*types.Named).Obj(); !obj.Exported() {
1110 log.Fatalf("exported method with unexported receiver base type: %s", m)
1111 }
1112 }
1113 tps := ""
1114 if rtp := sig.RecvTypeParams(); rtp != nil {
1115 var buf bytes.Buffer
1116 w.writeTypeParams(&buf, rtp, false)
1117 tps = buf.String()
1118 }
1119 if w.isDeprecated(m.Obj()) {
1120 w.emitf("method (%s%s) %s //deprecated", w.typeString(recv), tps, m.Obj().Name())
1121 }
1122 w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
1123 }
1124
1125 func (w *Walker) emitf(format string, args ...any) {
1126 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
1127 if strings.Contains(f, "\n") {
1128 panic("feature contains newlines: " + f)
1129 }
1130
1131 if _, dup := w.features[f]; dup {
1132 panic("duplicate feature inserted: " + f)
1133 }
1134 w.features[f] = true
1135
1136 if verbose {
1137 log.Printf("feature: %s", f)
1138 }
1139 }
1140
1141 func needApproval(filename string) bool {
1142 name := filepath.Base(filename)
1143 if name == "go1.txt" {
1144 return false
1145 }
1146 minor := strings.TrimSuffix(strings.TrimPrefix(name, "go1."), ".txt")
1147 n, err := strconv.Atoi(minor)
1148 if err != nil {
1149 log.Fatalf("unexpected api file: %v", name)
1150 }
1151 return n >= 19
1152 }
1153
1154 func (w *Walker) collectDeprecated() {
1155 isDeprecated := func(doc *ast.CommentGroup) bool {
1156 if doc != nil {
1157 for _, c := range doc.List {
1158 if strings.HasPrefix(c.Text, "// Deprecated:") {
1159 return true
1160 }
1161 }
1162 }
1163 return false
1164 }
1165
1166 w.deprecated = make(map[token.Pos]bool)
1167 mark := func(id *ast.Ident) {
1168 if id != nil {
1169 w.deprecated[id.Pos()] = true
1170 }
1171 }
1172 for _, file := range w.current.Files {
1173 ast.Inspect(file, func(n ast.Node) bool {
1174 switch n := n.(type) {
1175 case *ast.File:
1176 if isDeprecated(n.Doc) {
1177 mark(n.Name)
1178 }
1179 return true
1180 case *ast.GenDecl:
1181 if isDeprecated(n.Doc) {
1182 for _, spec := range n.Specs {
1183 switch spec := spec.(type) {
1184 case *ast.ValueSpec:
1185 for _, id := range spec.Names {
1186 mark(id)
1187 }
1188 case *ast.TypeSpec:
1189 mark(spec.Name)
1190 }
1191 }
1192 }
1193 return true
1194 case *ast.FuncDecl:
1195 if isDeprecated(n.Doc) {
1196 mark(n.Name)
1197 }
1198 return false
1199 case *ast.TypeSpec:
1200 if isDeprecated(n.Doc) {
1201 mark(n.Name)
1202 }
1203 return true
1204 case *ast.StructType:
1205 return true
1206 case *ast.InterfaceType:
1207 return true
1208 case *ast.FieldList:
1209 return true
1210 case *ast.ValueSpec:
1211 if isDeprecated(n.Doc) {
1212 for _, id := range n.Names {
1213 mark(id)
1214 }
1215 }
1216 return false
1217 case *ast.Field:
1218 if isDeprecated(n.Doc) {
1219 for _, id := range n.Names {
1220 mark(id)
1221 }
1222 if len(n.Names) == 0 {
1223
1224 typ := n.Type
1225 if ptr, ok := typ.(*ast.StarExpr); ok {
1226 typ = ptr.X
1227 }
1228 if id, ok := typ.(*ast.Ident); ok {
1229 mark(id)
1230 }
1231 }
1232 }
1233 return false
1234 default:
1235 return false
1236 }
1237 })
1238 }
1239 }
1240
1241 func (w *Walker) isDeprecated(obj types.Object) bool {
1242 return w.deprecated[obj.Pos()]
1243 }
1244
View as plain text