Source file
src/cmd/api/main_test.go
Documentation: cmd/api
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 "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
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)
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
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
223
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
250
251
252
253
254
255 } else if portRemoved(feature) {
256
257 } else if featureSet[featureWithoutContext(feature)] {
258
259 } else {
260 fmt.Fprintf(w, "-%s\n", feature)
261 ok = false
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
267 default:
268 take(&required)
269 take(&features)
270 }
271 }
272
273 return ok
274 }
275
276
277
278
279
280
281
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
296
297
298
299
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
306
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
359 imported map[string]*apiPackage
360 stdPackages []string
361 importMap map[string]map[string]string
362 importDir map[string]string
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
403 const usePkgCache = true
404
405 var (
406 pkgCache = map[string]*apiPackage{}
407 pkgTags = map[string][]string{}
408 )
409
410
411
412
413
414
415
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
428 key := dir
429
430
431
432
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
447 importDir map[string]string
448 importMap map[string]map[string]string
449 }
450
451 var listCache sync.Map
452
453
454
455
456
457 var listSem = make(chan semToken, 2)
458
459 type semToken struct{}
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476 func (w *Walker) loadImports() {
477 if w.context == nil {
478 return
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
518
519
520
521
522
523
524
525
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
554
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
577
578 var importing apiPackage
579
580
581 func (w *Walker) Import(name string) (*types.Package, error) {
582 return w.ImportFrom(name, "", 0)
583 }
584
585
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
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
628
629
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
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
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
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
699
700
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
725
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
767
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
870 fmt.Fprintf(buf, "$%d", typ.Index())
871
872 default:
873 panic(fmt.Sprintf("unknown type %T", typ))
874 }
875 }
876
877 func (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
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
894 func (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
911 func (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
927 func (w *Walker) typeString(typ types.Type) string {
928 var buf bytes.Buffer
929 w.writeType(&buf, typ)
930 return buf.String()
931 }
932
933 func (w *Walker) signatureString(sig *types.Signature) string {
934 var buf bytes.Buffer
935 w.writeSignature(&buf, sig)
936 return buf.String()
937 }
938
939 func (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
968 func (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
990 default:
991 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
992 }
993
994
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
1009
1010
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
1020 func (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
1045 func (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
1066
1067
1068
1069
1070
1071
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
1090 func (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
1101 func (w *Walker) emitMethod(m *types.Selection) {
1102 sig := m.Type().(*types.Signature)
1103 recv := sig.Recv().Type()
1104
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
1126 func (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
1142 func 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
1153 }
1154
1155 func (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
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
1205 case *ast.StructType:
1206 return true
1207 case *ast.InterfaceType:
1208 return true
1209 case *ast.FieldList:
1210 return true
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
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
1242 func (w *Walker) isDeprecated(obj types.Object) bool {
1243 return w.deprecated[obj.Pos()]
1244 }
1245
View as plain text