Source file
src/cmd/api/goapi.go
Documentation: cmd/api
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,
219 *allowNew && strings.Contains(runtime.Version(), "devel"))
220 }
221
222
223 func (w *Walker) export(pkg *types.Package) {
224 if *verbose {
225 log.Println(pkg)
226 }
227 pop := w.pushScope("pkg " + pkg.Path())
228 w.current = pkg
229 scope := pkg.Scope()
230 for _, name := range scope.Names() {
231 if token.IsExported(name) {
232 w.emitObj(scope.Lookup(name))
233 }
234 }
235 pop()
236 }
237
238 func set(items []string) map[string]bool {
239 s := make(map[string]bool)
240 for _, v := range items {
241 s[v] = true
242 }
243 return s
244 }
245
246 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
247
248 func featureWithoutContext(f string) string {
249 if !strings.Contains(f, "(") {
250 return f
251 }
252 return spaceParensRx.ReplaceAllString(f, "")
253 }
254
255
256
257 func portRemoved(feature string) bool {
258 return strings.Contains(feature, "(darwin-386)") ||
259 strings.Contains(feature, "(darwin-386-cgo)")
260 }
261
262 func compareAPI(w io.Writer, features, required, optional, exception []string, allowAdd bool) (ok bool) {
263 ok = true
264
265 optionalSet := set(optional)
266 exceptionSet := set(exception)
267 featureSet := set(features)
268
269 sort.Strings(features)
270 sort.Strings(required)
271
272 take := func(sl *[]string) string {
273 s := (*sl)[0]
274 *sl = (*sl)[1:]
275 return s
276 }
277
278 for len(required) > 0 || len(features) > 0 {
279 switch {
280 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
281 feature := take(&required)
282 if exceptionSet[feature] {
283
284
285
286
287
288
289 } else if portRemoved(feature) {
290
291 } else if featureSet[featureWithoutContext(feature)] {
292
293 } else {
294 fmt.Fprintf(w, "-%s\n", feature)
295 ok = false
296 }
297 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
298 newFeature := take(&features)
299 if optionalSet[newFeature] {
300
301
302
303 delete(optionalSet, newFeature)
304 } else {
305 fmt.Fprintf(w, "+%s\n", newFeature)
306 if !allowAdd {
307 ok = false
308 }
309 }
310 default:
311 take(&required)
312 take(&features)
313 }
314 }
315
316
317 var missing []string
318 for feature := range optionalSet {
319 missing = append(missing, feature)
320 }
321 sort.Strings(missing)
322 for _, feature := range missing {
323 fmt.Fprintf(w, "±%s\n", feature)
324 }
325 return
326 }
327
328
329
330
331
332
333
334 var aliasReplacer = strings.NewReplacer(
335 "os.FileInfo", "fs.FileInfo",
336 "os.FileMode", "fs.FileMode",
337 "os.PathError", "fs.PathError",
338 )
339
340 func fileFeatures(filename string) []string {
341 if filename == "" {
342 return nil
343 }
344 bs, err := os.ReadFile(filename)
345 if err != nil {
346 log.Fatalf("Error reading file %s: %v", filename, err)
347 }
348 s := string(bs)
349 s = aliasReplacer.Replace(s)
350 lines := strings.Split(s, "\n")
351 var nonblank []string
352 for _, line := range lines {
353 line = strings.TrimSpace(line)
354 if line != "" && !strings.HasPrefix(line, "#") {
355 nonblank = append(nonblank, line)
356 }
357 }
358 return nonblank
359 }
360
361 var fset = token.NewFileSet()
362
363 type Walker struct {
364 context *build.Context
365 root string
366 scope []string
367 current *types.Package
368 features map[string]bool
369 imported map[string]*types.Package
370 stdPackages []string
371 importMap map[string]map[string]string
372 importDir map[string]string
373
374 }
375
376 func NewWalker(context *build.Context, root string) *Walker {
377 w := &Walker{
378 context: context,
379 root: root,
380 features: map[string]bool{},
381 imported: map[string]*types.Package{"unsafe": types.Unsafe},
382 }
383 w.loadImports()
384 return w
385 }
386
387 func (w *Walker) Features() (fs []string) {
388 for f := range w.features {
389 fs = append(fs, f)
390 }
391 sort.Strings(fs)
392 return
393 }
394
395 var parsedFileCache = make(map[string]*ast.File)
396
397 func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
398 filename := filepath.Join(dir, file)
399 if f := parsedFileCache[filename]; f != nil {
400 return f, nil
401 }
402
403 f, err := parser.ParseFile(fset, filename, nil, 0)
404 if err != nil {
405 return nil, err
406 }
407 parsedFileCache[filename] = f
408
409 return f, nil
410 }
411
412
413 const usePkgCache = true
414
415 var (
416 pkgCache = map[string]*types.Package{}
417 pkgTags = map[string][]string{}
418 )
419
420
421
422
423
424
425
426 func tagKey(dir string, context *build.Context, tags []string) string {
427 ctags := map[string]bool{
428 context.GOOS: true,
429 context.GOARCH: true,
430 }
431 if context.CgoEnabled {
432 ctags["cgo"] = true
433 }
434 for _, tag := range context.BuildTags {
435 ctags[tag] = true
436 }
437
438 key := dir
439
440
441
442
443 tags = append(tags, context.GOOS, context.GOARCH)
444 sort.Strings(tags)
445
446 for _, tag := range tags {
447 if ctags[tag] {
448 key += "," + tag
449 ctags[tag] = false
450 }
451 }
452 return key
453 }
454
455 type listImports struct {
456 stdPackages []string
457 importDir map[string]string
458 importMap map[string]map[string]string
459 }
460
461 var listCache sync.Map
462
463
464 var listSem = make(chan semToken, runtime.GOMAXPROCS(0))
465
466 type semToken struct{}
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483 func (w *Walker) loadImports() {
484 if w.context == nil {
485 return
486 }
487
488 name := contextName(w.context)
489
490 imports, ok := listCache.Load(name)
491 if !ok {
492 listSem <- semToken{}
493 defer func() { <-listSem }()
494
495 cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
496 cmd.Env = listEnv(w.context)
497 if w.context.Dir != "" {
498 cmd.Dir = w.context.Dir
499 }
500 out, err := cmd.CombinedOutput()
501 if err != nil {
502 log.Fatalf("loading imports: %v\n%s", err, out)
503 }
504
505 var stdPackages []string
506 importMap := make(map[string]map[string]string)
507 importDir := make(map[string]string)
508 dec := json.NewDecoder(bytes.NewReader(out))
509 for {
510 var pkg struct {
511 ImportPath, Dir string
512 ImportMap map[string]string
513 Standard bool
514 }
515 err := dec.Decode(&pkg)
516 if err == io.EOF {
517 break
518 }
519 if err != nil {
520 log.Fatalf("go list: invalid output: %v", err)
521 }
522
523
524
525
526
527
528
529
530
531
532 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
533 stdPackages = append(stdPackages, ip)
534 }
535 importDir[pkg.ImportPath] = pkg.Dir
536 if len(pkg.ImportMap) > 0 {
537 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
538 }
539 for k, v := range pkg.ImportMap {
540 importMap[pkg.Dir][k] = v
541 }
542 }
543
544 sort.Strings(stdPackages)
545 imports = listImports{
546 stdPackages: stdPackages,
547 importMap: importMap,
548 importDir: importDir,
549 }
550 imports, _ = listCache.LoadOrStore(name, imports)
551 }
552
553 li := imports.(listImports)
554 w.stdPackages = li.stdPackages
555 w.importDir = li.importDir
556 w.importMap = li.importMap
557 }
558
559
560
561 func listEnv(c *build.Context) []string {
562 if c == nil {
563 return os.Environ()
564 }
565
566 environ := append(os.Environ(),
567 "GOOS="+c.GOOS,
568 "GOARCH="+c.GOARCH)
569 if c.CgoEnabled {
570 environ = append(environ, "CGO_ENABLED=1")
571 } else {
572 environ = append(environ, "CGO_ENABLED=0")
573 }
574 return environ
575 }
576
577
578
579 var importing types.Package
580
581 func (w *Walker) Import(name string) (*types.Package, error) {
582 return w.ImportFrom(name, "", 0)
583 }
584
585 func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
586 name := fromPath
587 if canonical, ok := w.importMap[fromDir][fromPath]; ok {
588 name = canonical
589 }
590
591 pkg := w.imported[name]
592 if pkg != nil {
593 if pkg == &importing {
594 log.Fatalf("cycle importing package %q", name)
595 }
596 return pkg, nil
597 }
598 w.imported[name] = &importing
599
600
601 dir := w.importDir[name]
602 if dir == "" {
603 dir = filepath.Join(w.root, filepath.FromSlash(name))
604 }
605 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
606 log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
607 }
608
609 context := w.context
610 if context == nil {
611 context = &build.Default
612 }
613
614
615
616
617 var key string
618 if usePkgCache {
619 if tags, ok := pkgTags[dir]; ok {
620 key = tagKey(dir, context, tags)
621 if pkg := pkgCache[key]; pkg != nil {
622 w.imported[name] = pkg
623 return pkg, nil
624 }
625 }
626 }
627
628 info, err := context.ImportDir(dir, 0)
629 if err != nil {
630 if _, nogo := err.(*build.NoGoError); nogo {
631 return nil, err
632 }
633 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
634 }
635
636
637 if usePkgCache {
638 if _, ok := pkgTags[dir]; !ok {
639 pkgTags[dir] = info.AllTags
640 key = tagKey(dir, context, info.AllTags)
641 }
642 }
643
644 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
645
646
647 var files []*ast.File
648 for _, file := range filenames {
649 f, err := w.parseFile(dir, file)
650 if err != nil {
651 log.Fatalf("error parsing package %s: %s", name, err)
652 }
653 files = append(files, f)
654 }
655
656
657 conf := types.Config{
658 IgnoreFuncBodies: true,
659 FakeImportC: true,
660 Importer: w,
661 }
662 pkg, err = conf.Check(name, fset, files, nil)
663 if err != nil {
664 ctxt := "<no context>"
665 if w.context != nil {
666 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
667 }
668 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
669 }
670
671 if usePkgCache {
672 pkgCache[key] = pkg
673 }
674
675 w.imported[name] = pkg
676 return pkg, nil
677 }
678
679
680
681
682 func (w *Walker) pushScope(name string) (popFunc func()) {
683 w.scope = append(w.scope, name)
684 return func() {
685 if len(w.scope) == 0 {
686 log.Fatalf("attempt to leave scope %q with empty scope list", name)
687 }
688 if w.scope[len(w.scope)-1] != name {
689 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
690 }
691 w.scope = w.scope[:len(w.scope)-1]
692 }
693 }
694
695 func sortedMethodNames(typ *types.Interface) []string {
696 n := typ.NumMethods()
697 list := make([]string, n)
698 for i := range list {
699 list[i] = typ.Method(i).Name()
700 }
701 sort.Strings(list)
702 return list
703 }
704
705 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
706 switch typ := typ.(type) {
707 case *types.Basic:
708 s := typ.Name()
709 switch typ.Kind() {
710 case types.UnsafePointer:
711 s = "unsafe.Pointer"
712 case types.UntypedBool:
713 s = "ideal-bool"
714 case types.UntypedInt:
715 s = "ideal-int"
716 case types.UntypedRune:
717
718
719 s = "ideal-char"
720 case types.UntypedFloat:
721 s = "ideal-float"
722 case types.UntypedComplex:
723 s = "ideal-complex"
724 case types.UntypedString:
725 s = "ideal-string"
726 case types.UntypedNil:
727 panic("should never see untyped nil type")
728 default:
729 switch s {
730 case "byte":
731 s = "uint8"
732 case "rune":
733 s = "int32"
734 }
735 }
736 buf.WriteString(s)
737
738 case *types.Array:
739 fmt.Fprintf(buf, "[%d]", typ.Len())
740 w.writeType(buf, typ.Elem())
741
742 case *types.Slice:
743 buf.WriteString("[]")
744 w.writeType(buf, typ.Elem())
745
746 case *types.Struct:
747 buf.WriteString("struct")
748
749 case *types.Pointer:
750 buf.WriteByte('*')
751 w.writeType(buf, typ.Elem())
752
753 case *types.Tuple:
754 panic("should never see a tuple type")
755
756 case *types.Signature:
757 buf.WriteString("func")
758 w.writeSignature(buf, typ)
759
760 case *types.Interface:
761 buf.WriteString("interface{")
762 if typ.NumMethods() > 0 {
763 buf.WriteByte(' ')
764 buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
765 buf.WriteByte(' ')
766 }
767 buf.WriteString("}")
768
769 case *types.Map:
770 buf.WriteString("map[")
771 w.writeType(buf, typ.Key())
772 buf.WriteByte(']')
773 w.writeType(buf, typ.Elem())
774
775 case *types.Chan:
776 var s string
777 switch typ.Dir() {
778 case types.SendOnly:
779 s = "chan<- "
780 case types.RecvOnly:
781 s = "<-chan "
782 case types.SendRecv:
783 s = "chan "
784 default:
785 panic("unreachable")
786 }
787 buf.WriteString(s)
788 w.writeType(buf, typ.Elem())
789
790 case *types.Named:
791 obj := typ.Obj()
792 pkg := obj.Pkg()
793 if pkg != nil && pkg != w.current {
794 buf.WriteString(pkg.Name())
795 buf.WriteByte('.')
796 }
797 buf.WriteString(typ.Obj().Name())
798
799 default:
800 panic(fmt.Sprintf("unknown type %T", typ))
801 }
802 }
803
804 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
805 w.writeParams(buf, sig.Params(), sig.Variadic())
806 switch res := sig.Results(); res.Len() {
807 case 0:
808
809 case 1:
810 buf.WriteByte(' ')
811 w.writeType(buf, res.At(0).Type())
812 default:
813 buf.WriteByte(' ')
814 w.writeParams(buf, res, false)
815 }
816 }
817
818 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
819 buf.WriteByte('(')
820 for i, n := 0, t.Len(); i < n; i++ {
821 if i > 0 {
822 buf.WriteString(", ")
823 }
824 typ := t.At(i).Type()
825 if variadic && i+1 == n {
826 buf.WriteString("...")
827 typ = typ.(*types.Slice).Elem()
828 }
829 w.writeType(buf, typ)
830 }
831 buf.WriteByte(')')
832 }
833
834 func (w *Walker) typeString(typ types.Type) string {
835 var buf bytes.Buffer
836 w.writeType(&buf, typ)
837 return buf.String()
838 }
839
840 func (w *Walker) signatureString(sig *types.Signature) string {
841 var buf bytes.Buffer
842 w.writeSignature(&buf, sig)
843 return buf.String()
844 }
845
846 func (w *Walker) emitObj(obj types.Object) {
847 switch obj := obj.(type) {
848 case *types.Const:
849 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
850 x := obj.Val()
851 short := x.String()
852 exact := x.ExactString()
853 if short == exact {
854 w.emitf("const %s = %s", obj.Name(), short)
855 } else {
856 w.emitf("const %s = %s // %s", obj.Name(), short, exact)
857 }
858 case *types.Var:
859 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
860 case *types.TypeName:
861 w.emitType(obj)
862 case *types.Func:
863 w.emitFunc(obj)
864 default:
865 panic("unknown object: " + obj.String())
866 }
867 }
868
869 func (w *Walker) emitType(obj *types.TypeName) {
870 name := obj.Name()
871 typ := obj.Type()
872 if obj.IsAlias() {
873 w.emitf("type %s = %s", name, w.typeString(typ))
874 return
875 }
876 switch typ := typ.Underlying().(type) {
877 case *types.Struct:
878 w.emitStructType(name, typ)
879 case *types.Interface:
880 w.emitIfaceType(name, typ)
881 return
882 default:
883 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
884 }
885
886
887 var methodNames map[string]bool
888 vset := types.NewMethodSet(typ)
889 for i, n := 0, vset.Len(); i < n; i++ {
890 m := vset.At(i)
891 if m.Obj().Exported() {
892 w.emitMethod(m)
893 if methodNames == nil {
894 methodNames = make(map[string]bool)
895 }
896 methodNames[m.Obj().Name()] = true
897 }
898 }
899
900
901
902
903 pset := types.NewMethodSet(types.NewPointer(typ))
904 for i, n := 0, pset.Len(); i < n; i++ {
905 m := pset.At(i)
906 if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
907 w.emitMethod(m)
908 }
909 }
910 }
911
912 func (w *Walker) emitStructType(name string, typ *types.Struct) {
913 typeStruct := fmt.Sprintf("type %s struct", name)
914 w.emitf(typeStruct)
915 defer w.pushScope(typeStruct)()
916
917 for i := 0; i < typ.NumFields(); i++ {
918 f := typ.Field(i)
919 if !f.Exported() {
920 continue
921 }
922 typ := f.Type()
923 if f.Anonymous() {
924 w.emitf("embedded %s", w.typeString(typ))
925 continue
926 }
927 w.emitf("%s %s", f.Name(), w.typeString(typ))
928 }
929 }
930
931 func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
932 pop := w.pushScope("type " + name + " interface")
933
934 var methodNames []string
935 complete := true
936 mset := types.NewMethodSet(typ)
937 for i, n := 0, mset.Len(); i < n; i++ {
938 m := mset.At(i).Obj().(*types.Func)
939 if !m.Exported() {
940 complete = false
941 continue
942 }
943 methodNames = append(methodNames, m.Name())
944 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
945 }
946
947 if !complete {
948
949
950
951
952
953
954
955 w.emitf("unexported methods")
956 }
957
958 pop()
959
960 if !complete {
961 return
962 }
963
964 if len(methodNames) == 0 {
965 w.emitf("type %s interface {}", name)
966 return
967 }
968
969 sort.Strings(methodNames)
970 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
971 }
972
973 func (w *Walker) emitFunc(f *types.Func) {
974 sig := f.Type().(*types.Signature)
975 if sig.Recv() != nil {
976 panic("method considered a regular function: " + f.String())
977 }
978 w.emitf("func %s%s", f.Name(), w.signatureString(sig))
979 }
980
981 func (w *Walker) emitMethod(m *types.Selection) {
982 sig := m.Type().(*types.Signature)
983 recv := sig.Recv().Type()
984
985 if true {
986 base := recv
987 if p, _ := recv.(*types.Pointer); p != nil {
988 base = p.Elem()
989 }
990 if obj := base.(*types.Named).Obj(); !obj.Exported() {
991 log.Fatalf("exported method with unexported receiver base type: %s", m)
992 }
993 }
994 w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig))
995 }
996
997 func (w *Walker) emitf(format string, args ...interface{}) {
998 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
999 if strings.Contains(f, "\n") {
1000 panic("feature contains newlines: " + f)
1001 }
1002
1003 if _, dup := w.features[f]; dup {
1004 panic("duplicate feature inserted: " + f)
1005 }
1006 w.features[f] = true
1007
1008 if *verbose {
1009 log.Printf("feature: %s", f)
1010 }
1011 }
1012
View as plain text