Source file
src/cmd/api/goapi.go
1
2
3
4
5
6 package main
7
8 import (
9 "bufio"
10 "bytes"
11 "encoding/json"
12 "flag"
13 "fmt"
14 "go/ast"
15 "go/build"
16 "go/parser"
17 "go/token"
18 "go/types"
19 exec "internal/execabs"
20 "io"
21 "log"
22 "os"
23 "path/filepath"
24 "regexp"
25 "runtime"
26 "sort"
27 "strings"
28 "sync"
29 )
30
31 func goCmd() string {
32 var exeSuffix string
33 if runtime.GOOS == "windows" {
34 exeSuffix = ".exe"
35 }
36 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
37 if _, err := os.Stat(path); err == nil {
38 return path
39 }
40 return "go"
41 }
42
43
44 var (
45 checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against")
46 allowNew = flag.Bool("allow_new", true, "allow API additions")
47 exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
48 nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
49 verbose = flag.Bool("v", false, "verbose debugging")
50 forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
51 )
52
53
54
55 var contexts = []*build.Context{
56 {GOOS: "linux", GOARCH: "386", CgoEnabled: true},
57 {GOOS: "linux", GOARCH: "386"},
58 {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true},
59 {GOOS: "linux", GOARCH: "amd64"},
60 {GOOS: "linux", GOARCH: "arm", CgoEnabled: true},
61 {GOOS: "linux", GOARCH: "arm"},
62 {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true},
63 {GOOS: "darwin", GOARCH: "amd64"},
64 {GOOS: "windows", GOARCH: "amd64"},
65 {GOOS: "windows", GOARCH: "386"},
66 {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true},
67 {GOOS: "freebsd", GOARCH: "386"},
68 {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true},
69 {GOOS: "freebsd", GOARCH: "amd64"},
70 {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true},
71 {GOOS: "freebsd", GOARCH: "arm"},
72 {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true},
73 {GOOS: "netbsd", GOARCH: "386"},
74 {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true},
75 {GOOS: "netbsd", GOARCH: "amd64"},
76 {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true},
77 {GOOS: "netbsd", GOARCH: "arm"},
78 {GOOS: "netbsd", GOARCH: "arm64", CgoEnabled: true},
79 {GOOS: "netbsd", GOARCH: "arm64"},
80 {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true},
81 {GOOS: "openbsd", GOARCH: "386"},
82 {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true},
83 {GOOS: "openbsd", GOARCH: "amd64"},
84 }
85
86 func contextName(c *build.Context) string {
87 s := c.GOOS + "-" + c.GOARCH
88 if c.CgoEnabled {
89 s += "-cgo"
90 }
91 if c.Dir != "" {
92 s += fmt.Sprintf(" [%s]", c.Dir)
93 }
94 return s
95 }
96
97 func parseContext(c string) *build.Context {
98 parts := strings.Split(c, "-")
99 if len(parts) < 2 {
100 log.Fatalf("bad context: %q", c)
101 }
102 bc := &build.Context{
103 GOOS: parts[0],
104 GOARCH: parts[1],
105 }
106 if len(parts) == 3 {
107 if parts[2] == "cgo" {
108 bc.CgoEnabled = true
109 } else {
110 log.Fatalf("bad context: %q", c)
111 }
112 }
113 return bc
114 }
115
116 func setContexts() {
117 contexts = []*build.Context{}
118 for _, c := range strings.Split(*forceCtx, ",") {
119 contexts = append(contexts, parseContext(c))
120 }
121 }
122
123 var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
124
125 func main() {
126 flag.Parse()
127
128 if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
129 if *nextFile != "" {
130 fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
131 *nextFile = ""
132 }
133 }
134
135 if *forceCtx != "" {
136 setContexts()
137 }
138 for _, c := range contexts {
139 c.Compiler = build.Default.Compiler
140 }
141
142 walkers := make([]*Walker, len(contexts))
143 var wg sync.WaitGroup
144 for i, context := range contexts {
145 i, context := i, context
146 wg.Add(1)
147 go func() {
148 defer wg.Done()
149 walkers[i] = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
150 }()
151 }
152 wg.Wait()
153
154 var featureCtx = make(map[string]map[string]bool)
155 for _, w := range walkers {
156 pkgNames := w.stdPackages
157 if flag.NArg() > 0 {
158 pkgNames = flag.Args()
159 }
160
161 for _, name := range pkgNames {
162 pkg, err := w.Import(name)
163 if _, nogo := err.(*build.NoGoError); nogo {
164 continue
165 }
166 if err != nil {
167 log.Fatalf("Import(%q): %v", name, err)
168 }
169 w.export(pkg)
170 }
171
172 ctxName := contextName(w.context)
173 for _, f := range w.Features() {
174 if featureCtx[f] == nil {
175 featureCtx[f] = make(map[string]bool)
176 }
177 featureCtx[f][ctxName] = true
178 }
179 }
180
181 var features []string
182 for f, cmap := range featureCtx {
183 if len(cmap) == len(contexts) {
184 features = append(features, f)
185 continue
186 }
187 comma := strings.Index(f, ",")
188 for cname := range cmap {
189 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
190 features = append(features, f2)
191 }
192 }
193
194 fail := false
195 defer func() {
196 if fail {
197 os.Exit(1)
198 }
199 }()
200
201 bw := bufio.NewWriter(os.Stdout)
202 defer bw.Flush()
203
204 if *checkFile == "" {
205 sort.Strings(features)
206 for _, f := range features {
207 fmt.Fprintln(bw, f)
208 }
209 return
210 }
211
212 var required []string
213 for _, file := range strings.Split(*checkFile, ",") {
214 required = append(required, fileFeatures(file)...)
215 }
216 optional := fileFeatures(*nextFile)
217 exception := fileFeatures(*exceptFile)
218 fail = !compareAPI(bw, features, required, optional, exception, *allowNew)
219 }
220
221
222 func (w *Walker) export(pkg *types.Package) {
223 if *verbose {
224 log.Println(pkg)
225 }
226 pop := w.pushScope("pkg " + pkg.Path())
227 w.current = pkg
228 scope := pkg.Scope()
229 for _, name := range scope.Names() {
230 if token.IsExported(name) {
231 w.emitObj(scope.Lookup(name))
232 }
233 }
234 pop()
235 }
236
237 func set(items []string) map[string]bool {
238 s := make(map[string]bool)
239 for _, v := range items {
240 s[v] = true
241 }
242 return s
243 }
244
245 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
246
247 func featureWithoutContext(f string) string {
248 if !strings.Contains(f, "(") {
249 return f
250 }
251 return spaceParensRx.ReplaceAllString(f, "")
252 }
253
254
255
256 func portRemoved(feature string) bool {
257 return strings.Contains(feature, "(darwin-386)") ||
258 strings.Contains(feature, "(darwin-386-cgo)")
259 }
260
261 func compareAPI(w io.Writer, features, required, optional, exception []string, allowAdd bool) (ok bool) {
262 ok = true
263
264 optionalSet := set(optional)
265 exceptionSet := set(exception)
266 featureSet := set(features)
267
268 sort.Strings(features)
269 sort.Strings(required)
270
271 take := func(sl *[]string) string {
272 s := (*sl)[0]
273 *sl = (*sl)[1:]
274 return s
275 }
276
277 for len(required) > 0 || len(features) > 0 {
278 switch {
279 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
280 feature := take(&required)
281 if exceptionSet[feature] {
282
283
284
285
286
287
288 } else if portRemoved(feature) {
289
290 } else if featureSet[featureWithoutContext(feature)] {
291
292 } else {
293 fmt.Fprintf(w, "-%s\n", feature)
294 ok = false
295 }
296 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
297 newFeature := take(&features)
298 if optionalSet[newFeature] {
299
300
301
302 delete(optionalSet, newFeature)
303 } else {
304 fmt.Fprintf(w, "+%s\n", newFeature)
305 if !allowAdd {
306 ok = false
307 }
308 }
309 default:
310 take(&required)
311 take(&features)
312 }
313 }
314
315
316 var missing []string
317 for feature := range optionalSet {
318 missing = append(missing, feature)
319 }
320 sort.Strings(missing)
321 for _, feature := range missing {
322 fmt.Fprintf(w, "±%s\n", feature)
323 }
324 return
325 }
326
327
328
329
330
331
332
333 var aliasReplacer = strings.NewReplacer(
334 "os.FileInfo", "fs.FileInfo",
335 "os.FileMode", "fs.FileMode",
336 "os.PathError", "fs.PathError",
337 )
338
339 func fileFeatures(filename string) []string {
340 if filename == "" {
341 return nil
342 }
343 bs, err := os.ReadFile(filename)
344 if err != nil {
345 log.Fatalf("Error reading file %s: %v", filename, err)
346 }
347 s := string(bs)
348 s = aliasReplacer.Replace(s)
349 lines := strings.Split(s, "\n")
350 var nonblank []string
351 for _, line := range lines {
352 line = strings.TrimSpace(line)
353 if line != "" && !strings.HasPrefix(line, "#") {
354 nonblank = append(nonblank, line)
355 }
356 }
357 return nonblank
358 }
359
360 var fset = token.NewFileSet()
361
362 type Walker struct {
363 context *build.Context
364 root string
365 scope []string
366 current *types.Package
367 features map[string]bool
368 imported map[string]*types.Package
369 stdPackages []string
370 importMap map[string]map[string]string
371 importDir map[string]string
372
373 }
374
375 func NewWalker(context *build.Context, root string) *Walker {
376 w := &Walker{
377 context: context,
378 root: root,
379 features: map[string]bool{},
380 imported: map[string]*types.Package{"unsafe": types.Unsafe},
381 }
382 w.loadImports()
383 return w
384 }
385
386 func (w *Walker) Features() (fs []string) {
387 for f := range w.features {
388 fs = append(fs, f)
389 }
390 sort.Strings(fs)
391 return
392 }
393
394 var parsedFileCache = make(map[string]*ast.File)
395
396 func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
397 filename := filepath.Join(dir, file)
398 if f := parsedFileCache[filename]; f != nil {
399 return f, nil
400 }
401
402 f, err := parser.ParseFile(fset, filename, nil, 0)
403 if err != nil {
404 return nil, err
405 }
406 parsedFileCache[filename] = f
407
408 return f, nil
409 }
410
411
412 const usePkgCache = true
413
414 var (
415 pkgCache = map[string]*types.Package{}
416 pkgTags = map[string][]string{}
417 )
418
419
420
421
422
423
424
425 func tagKey(dir string, context *build.Context, tags []string) string {
426 ctags := map[string]bool{
427 context.GOOS: true,
428 context.GOARCH: true,
429 }
430 if context.CgoEnabled {
431 ctags["cgo"] = true
432 }
433 for _, tag := range context.BuildTags {
434 ctags[tag] = true
435 }
436
437 key := dir
438
439
440
441
442 tags = append(tags, context.GOOS, context.GOARCH)
443 sort.Strings(tags)
444
445 for _, tag := range tags {
446 if ctags[tag] {
447 key += "," + tag
448 ctags[tag] = false
449 }
450 }
451 return key
452 }
453
454 type listImports struct {
455 stdPackages []string
456 importDir map[string]string
457 importMap map[string]map[string]string
458 }
459
460 var listCache sync.Map
461
462
463
464
465
466 var listSem = make(chan semToken, 2)
467
468 type semToken struct{}
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 func (w *Walker) loadImports() {
486 if w.context == nil {
487 return
488 }
489
490 name := contextName(w.context)
491
492 imports, ok := listCache.Load(name)
493 if !ok {
494 listSem <- semToken{}
495 defer func() { <-listSem }()
496
497 cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
498 cmd.Env = listEnv(w.context)
499 if w.context.Dir != "" {
500 cmd.Dir = w.context.Dir
501 }
502 out, err := cmd.CombinedOutput()
503 if err != nil {
504 log.Fatalf("loading imports: %v\n%s", err, out)
505 }
506
507 var stdPackages []string
508 importMap := make(map[string]map[string]string)
509 importDir := make(map[string]string)
510 dec := json.NewDecoder(bytes.NewReader(out))
511 for {
512 var pkg struct {
513 ImportPath, Dir string
514 ImportMap map[string]string
515 Standard bool
516 }
517 err := dec.Decode(&pkg)
518 if err == io.EOF {
519 break
520 }
521 if err != nil {
522 log.Fatalf("go list: invalid output: %v", err)
523 }
524
525
526
527
528
529
530
531
532
533
534 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
535 stdPackages = append(stdPackages, ip)
536 }
537 importDir[pkg.ImportPath] = pkg.Dir
538 if len(pkg.ImportMap) > 0 {
539 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
540 }
541 for k, v := range pkg.ImportMap {
542 importMap[pkg.Dir][k] = v
543 }
544 }
545
546 sort.Strings(stdPackages)
547 imports = listImports{
548 stdPackages: stdPackages,
549 importMap: importMap,
550 importDir: importDir,
551 }
552 imports, _ = listCache.LoadOrStore(name, imports)
553 }
554
555 li := imports.(listImports)
556 w.stdPackages = li.stdPackages
557 w.importDir = li.importDir
558 w.importMap = li.importMap
559 }
560
561
562
563 func listEnv(c *build.Context) []string {
564 if c == nil {
565 return os.Environ()
566 }
567
568 environ := append(os.Environ(),
569 "GOOS="+c.GOOS,
570 "GOARCH="+c.GOARCH)
571 if c.CgoEnabled {
572 environ = append(environ, "CGO_ENABLED=1")
573 } else {
574 environ = append(environ, "CGO_ENABLED=0")
575 }
576 return environ
577 }
578
579
580
581 var importing types.Package
582
583 func (w *Walker) Import(name string) (*types.Package, error) {
584 return w.ImportFrom(name, "", 0)
585 }
586
587 func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
588 name := fromPath
589 if canonical, ok := w.importMap[fromDir][fromPath]; ok {
590 name = canonical
591 }
592
593 pkg := w.imported[name]
594 if pkg != nil {
595 if pkg == &importing {
596 log.Fatalf("cycle importing package %q", name)
597 }
598 return pkg, nil
599 }
600 w.imported[name] = &importing
601
602
603 dir := w.importDir[name]
604 if dir == "" {
605 dir = filepath.Join(w.root, filepath.FromSlash(name))
606 }
607 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
608 log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
609 }
610
611 context := w.context
612 if context == nil {
613 context = &build.Default
614 }
615
616
617
618
619 var key string
620 if usePkgCache {
621 if tags, ok := pkgTags[dir]; ok {
622 key = tagKey(dir, context, tags)
623 if pkg := pkgCache[key]; pkg != nil {
624 w.imported[name] = pkg
625 return pkg, nil
626 }
627 }
628 }
629
630 info, err := context.ImportDir(dir, 0)
631 if err != nil {
632 if _, nogo := err.(*build.NoGoError); nogo {
633 return nil, err
634 }
635 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
636 }
637
638
639 if usePkgCache {
640 if _, ok := pkgTags[dir]; !ok {
641 pkgTags[dir] = info.AllTags
642 key = tagKey(dir, context, info.AllTags)
643 }
644 }
645
646 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
647
648
649 var files []*ast.File
650 for _, file := range filenames {
651 f, err := w.parseFile(dir, file)
652 if err != nil {
653 log.Fatalf("error parsing package %s: %s", name, err)
654 }
655 files = append(files, f)
656 }
657
658
659 var sizes types.Sizes
660 if w.context != nil {
661 sizes = types.SizesFor(w.context.Compiler, w.context.GOARCH)
662 }
663 conf := types.Config{
664 IgnoreFuncBodies: true,
665 FakeImportC: true,
666 Importer: w,
667 Sizes: sizes,
668 }
669 pkg, err = conf.Check(name, fset, files, nil)
670 if err != nil {
671 ctxt := "<no context>"
672 if w.context != nil {
673 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
674 }
675 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
676 }
677
678 if usePkgCache {
679 pkgCache[key] = pkg
680 }
681
682 w.imported[name] = pkg
683 return pkg, nil
684 }
685
686
687
688
689 func (w *Walker) pushScope(name string) (popFunc func()) {
690 w.scope = append(w.scope, name)
691 return func() {
692 if len(w.scope) == 0 {
693 log.Fatalf("attempt to leave scope %q with empty scope list", name)
694 }
695 if w.scope[len(w.scope)-1] != name {
696 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
697 }
698 w.scope = w.scope[:len(w.scope)-1]
699 }
700 }
701
702 func sortedMethodNames(typ *types.Interface) []string {
703 n := typ.NumMethods()
704 list := make([]string, n)
705 for i := range list {
706 list[i] = typ.Method(i).Name()
707 }
708 sort.Strings(list)
709 return list
710 }
711
712
713
714 func (w *Walker) sortedEmbeddeds(typ *types.Interface) []string {
715 n := typ.NumEmbeddeds()
716 list := make([]string, 0, n)
717 for i := 0; i < n; i++ {
718 emb := typ.EmbeddedType(i)
719 switch emb := emb.(type) {
720 case *types.Interface:
721 list = append(list, w.sortedEmbeddeds(emb)...)
722 case *types.Union:
723 var buf bytes.Buffer
724 nu := emb.Len()
725 for i := 0; i < nu; i++ {
726 if i > 0 {
727 buf.WriteString(" | ")
728 }
729 term := emb.Term(i)
730 if term.Tilde() {
731 buf.WriteByte('~')
732 }
733 w.writeType(&buf, term.Type())
734 }
735 list = append(list, buf.String())
736 }
737 }
738 sort.Strings(list)
739 return list
740 }
741
742 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
743 switch typ := typ.(type) {
744 case *types.Basic:
745 s := typ.Name()
746 switch typ.Kind() {
747 case types.UnsafePointer:
748 s = "unsafe.Pointer"
749 case types.UntypedBool:
750 s = "ideal-bool"
751 case types.UntypedInt:
752 s = "ideal-int"
753 case types.UntypedRune:
754
755
756 s = "ideal-char"
757 case types.UntypedFloat:
758 s = "ideal-float"
759 case types.UntypedComplex:
760 s = "ideal-complex"
761 case types.UntypedString:
762 s = "ideal-string"
763 case types.UntypedNil:
764 panic("should never see untyped nil type")
765 default:
766 switch s {
767 case "byte":
768 s = "uint8"
769 case "rune":
770 s = "int32"
771 }
772 }
773 buf.WriteString(s)
774
775 case *types.Array:
776 fmt.Fprintf(buf, "[%d]", typ.Len())
777 w.writeType(buf, typ.Elem())
778
779 case *types.Slice:
780 buf.WriteString("[]")
781 w.writeType(buf, typ.Elem())
782
783 case *types.Struct:
784 buf.WriteString("struct")
785
786 case *types.Pointer:
787 buf.WriteByte('*')
788 w.writeType(buf, typ.Elem())
789
790 case *types.Tuple:
791 panic("should never see a tuple type")
792
793 case *types.Signature:
794 buf.WriteString("func")
795 w.writeSignature(buf, typ)
796
797 case *types.Interface:
798 buf.WriteString("interface{")
799 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
800 buf.WriteByte(' ')
801 }
802 if typ.NumMethods() > 0 {
803 buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
804 }
805 if typ.NumEmbeddeds() > 0 {
806 buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", "))
807 }
808 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 {
809 buf.WriteByte(' ')
810 }
811 buf.WriteString("}")
812
813 case *types.Map:
814 buf.WriteString("map[")
815 w.writeType(buf, typ.Key())
816 buf.WriteByte(']')
817 w.writeType(buf, typ.Elem())
818
819 case *types.Chan:
820 var s string
821 switch typ.Dir() {
822 case types.SendOnly:
823 s = "chan<- "
824 case types.RecvOnly:
825 s = "<-chan "
826 case types.SendRecv:
827 s = "chan "
828 default:
829 panic("unreachable")
830 }
831 buf.WriteString(s)
832 w.writeType(buf, typ.Elem())
833
834 case *types.Named:
835 obj := typ.Obj()
836 pkg := obj.Pkg()
837 if pkg != nil && pkg != w.current {
838 buf.WriteString(pkg.Name())
839 buf.WriteByte('.')
840 }
841 buf.WriteString(typ.Obj().Name())
842
843 case *types.TypeParam:
844
845 fmt.Fprintf(buf, "$%d", typ.Index())
846
847 default:
848 panic(fmt.Sprintf("unknown type %T", typ))
849 }
850 }
851
852 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
853 if tparams := sig.TypeParams(); tparams != nil {
854 w.writeTypeParams(buf, tparams, true)
855 }
856 w.writeParams(buf, sig.Params(), sig.Variadic())
857 switch res := sig.Results(); res.Len() {
858 case 0:
859
860 case 1:
861 buf.WriteByte(' ')
862 w.writeType(buf, res.At(0).Type())
863 default:
864 buf.WriteByte(' ')
865 w.writeParams(buf, res, false)
866 }
867 }
868
869 func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) {
870 buf.WriteByte('[')
871 c := tparams.Len()
872 for i := 0; i < c; i++ {
873 if i > 0 {
874 buf.WriteString(", ")
875 }
876 tp := tparams.At(i)
877 w.writeType(buf, tp)
878 if withConstraints {
879 buf.WriteByte(' ')
880 w.writeType(buf, tp.Constraint())
881 }
882 }
883 buf.WriteByte(']')
884 }
885
886 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
887 buf.WriteByte('(')
888 for i, n := 0, t.Len(); i < n; i++ {
889 if i > 0 {
890 buf.WriteString(", ")
891 }
892 typ := t.At(i).Type()
893 if variadic && i+1 == n {
894 buf.WriteString("...")
895 typ = typ.(*types.Slice).Elem()
896 }
897 w.writeType(buf, typ)
898 }
899 buf.WriteByte(')')
900 }
901
902 func (w *Walker) typeString(typ types.Type) string {
903 var buf bytes.Buffer
904 w.writeType(&buf, typ)
905 return buf.String()
906 }
907
908 func (w *Walker) signatureString(sig *types.Signature) string {
909 var buf bytes.Buffer
910 w.writeSignature(&buf, sig)
911 return buf.String()
912 }
913
914 func (w *Walker) emitObj(obj types.Object) {
915 switch obj := obj.(type) {
916 case *types.Const:
917 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
918 x := obj.Val()
919 short := x.String()
920 exact := x.ExactString()
921 if short == exact {
922 w.emitf("const %s = %s", obj.Name(), short)
923 } else {
924 w.emitf("const %s = %s // %s", obj.Name(), short, exact)
925 }
926 case *types.Var:
927 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
928 case *types.TypeName:
929 w.emitType(obj)
930 case *types.Func:
931 w.emitFunc(obj)
932 default:
933 panic("unknown object: " + obj.String())
934 }
935 }
936
937 func (w *Walker) emitType(obj *types.TypeName) {
938 name := obj.Name()
939 if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
940 var buf bytes.Buffer
941 buf.WriteString(name)
942 w.writeTypeParams(&buf, tparams, true)
943 name = buf.String()
944 }
945 typ := obj.Type()
946 if obj.IsAlias() {
947 w.emitf("type %s = %s", name, w.typeString(typ))
948 return
949 }
950 switch typ := typ.Underlying().(type) {
951 case *types.Struct:
952 w.emitStructType(name, typ)
953 case *types.Interface:
954 w.emitIfaceType(name, typ)
955 return
956 default:
957 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
958 }
959
960
961 var methodNames map[string]bool
962 vset := types.NewMethodSet(typ)
963 for i, n := 0, vset.Len(); i < n; i++ {
964 m := vset.At(i)
965 if m.Obj().Exported() {
966 w.emitMethod(m)
967 if methodNames == nil {
968 methodNames = make(map[string]bool)
969 }
970 methodNames[m.Obj().Name()] = true
971 }
972 }
973
974
975
976
977 pset := types.NewMethodSet(types.NewPointer(typ))
978 for i, n := 0, pset.Len(); i < n; i++ {
979 m := pset.At(i)
980 if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
981 w.emitMethod(m)
982 }
983 }
984 }
985
986 func (w *Walker) emitStructType(name string, typ *types.Struct) {
987 typeStruct := fmt.Sprintf("type %s struct", name)
988 w.emitf(typeStruct)
989 defer w.pushScope(typeStruct)()
990
991 for i := 0; i < typ.NumFields(); i++ {
992 f := typ.Field(i)
993 if !f.Exported() {
994 continue
995 }
996 typ := f.Type()
997 if f.Anonymous() {
998 w.emitf("embedded %s", w.typeString(typ))
999 continue
1000 }
1001 w.emitf("%s %s", f.Name(), w.typeString(typ))
1002 }
1003 }
1004
1005 func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
1006 pop := w.pushScope("type " + name + " interface")
1007
1008 var methodNames []string
1009 complete := true
1010 mset := types.NewMethodSet(typ)
1011 for i, n := 0, mset.Len(); i < n; i++ {
1012 m := mset.At(i).Obj().(*types.Func)
1013 if !m.Exported() {
1014 complete = false
1015 continue
1016 }
1017 methodNames = append(methodNames, m.Name())
1018 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
1019 }
1020
1021 if !complete {
1022
1023
1024
1025
1026
1027
1028
1029 w.emitf("unexported methods")
1030 }
1031
1032 pop()
1033
1034 if !complete {
1035 return
1036 }
1037
1038 if len(methodNames) == 0 {
1039 w.emitf("type %s interface {}", name)
1040 return
1041 }
1042
1043 sort.Strings(methodNames)
1044 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
1045 }
1046
1047 func (w *Walker) emitFunc(f *types.Func) {
1048 sig := f.Type().(*types.Signature)
1049 if sig.Recv() != nil {
1050 panic("method considered a regular function: " + f.String())
1051 }
1052 w.emitf("func %s%s", f.Name(), w.signatureString(sig))
1053 }
1054
1055 func (w *Walker) emitMethod(m *types.Selection) {
1056 sig := m.Type().(*types.Signature)
1057 recv := sig.Recv().Type()
1058
1059 if true {
1060 base := recv
1061 if p, _ := recv.(*types.Pointer); p != nil {
1062 base = p.Elem()
1063 }
1064 if obj := base.(*types.Named).Obj(); !obj.Exported() {
1065 log.Fatalf("exported method with unexported receiver base type: %s", m)
1066 }
1067 }
1068 tps := ""
1069 if rtp := sig.RecvTypeParams(); rtp != nil {
1070 var buf bytes.Buffer
1071 w.writeTypeParams(&buf, rtp, false)
1072 tps = buf.String()
1073 }
1074 w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig))
1075 }
1076
1077 func (w *Walker) emitf(format string, args ...any) {
1078 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
1079 if strings.Contains(f, "\n") {
1080 panic("feature contains newlines: " + f)
1081 }
1082
1083 if _, dup := w.features[f]; dup {
1084 panic("duplicate feature inserted: " + f)
1085 }
1086 w.features[f] = true
1087
1088 if *verbose {
1089 log.Printf("feature: %s", f)
1090 }
1091 }
1092
View as plain text