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, *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 var listSem = make(chan semToken, runtime.GOMAXPROCS(0))
464
465 type semToken struct{}
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482 func (w *Walker) loadImports() {
483 if w.context == nil {
484 return
485 }
486
487 name := contextName(w.context)
488
489 imports, ok := listCache.Load(name)
490 if !ok {
491 listSem <- semToken{}
492 defer func() { <-listSem }()
493
494 cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
495 cmd.Env = listEnv(w.context)
496 if w.context.Dir != "" {
497 cmd.Dir = w.context.Dir
498 }
499 out, err := cmd.CombinedOutput()
500 if err != nil {
501 log.Fatalf("loading imports: %v\n%s", err, out)
502 }
503
504 var stdPackages []string
505 importMap := make(map[string]map[string]string)
506 importDir := make(map[string]string)
507 dec := json.NewDecoder(bytes.NewReader(out))
508 for {
509 var pkg struct {
510 ImportPath, Dir string
511 ImportMap map[string]string
512 Standard bool
513 }
514 err := dec.Decode(&pkg)
515 if err == io.EOF {
516 break
517 }
518 if err != nil {
519 log.Fatalf("go list: invalid output: %v", err)
520 }
521
522
523
524
525
526
527
528
529
530
531 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
532 stdPackages = append(stdPackages, ip)
533 }
534 importDir[pkg.ImportPath] = pkg.Dir
535 if len(pkg.ImportMap) > 0 {
536 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
537 }
538 for k, v := range pkg.ImportMap {
539 importMap[pkg.Dir][k] = v
540 }
541 }
542
543 sort.Strings(stdPackages)
544 imports = listImports{
545 stdPackages: stdPackages,
546 importMap: importMap,
547 importDir: importDir,
548 }
549 imports, _ = listCache.LoadOrStore(name, imports)
550 }
551
552 li := imports.(listImports)
553 w.stdPackages = li.stdPackages
554 w.importDir = li.importDir
555 w.importMap = li.importMap
556 }
557
558
559
560 func listEnv(c *build.Context) []string {
561 if c == nil {
562 return os.Environ()
563 }
564
565 environ := append(os.Environ(),
566 "GOOS="+c.GOOS,
567 "GOARCH="+c.GOARCH)
568 if c.CgoEnabled {
569 environ = append(environ, "CGO_ENABLED=1")
570 } else {
571 environ = append(environ, "CGO_ENABLED=0")
572 }
573 return environ
574 }
575
576
577
578 var importing types.Package
579
580 func (w *Walker) Import(name string) (*types.Package, error) {
581 return w.ImportFrom(name, "", 0)
582 }
583
584 func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) {
585 name := fromPath
586 if canonical, ok := w.importMap[fromDir][fromPath]; ok {
587 name = canonical
588 }
589
590 pkg := w.imported[name]
591 if pkg != nil {
592 if pkg == &importing {
593 log.Fatalf("cycle importing package %q", name)
594 }
595 return pkg, nil
596 }
597 w.imported[name] = &importing
598
599
600 dir := w.importDir[name]
601 if dir == "" {
602 dir = filepath.Join(w.root, filepath.FromSlash(name))
603 }
604 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
605 log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
606 }
607
608 context := w.context
609 if context == nil {
610 context = &build.Default
611 }
612
613
614
615
616 var key string
617 if usePkgCache {
618 if tags, ok := pkgTags[dir]; ok {
619 key = tagKey(dir, context, tags)
620 if pkg := pkgCache[key]; pkg != nil {
621 w.imported[name] = pkg
622 return pkg, nil
623 }
624 }
625 }
626
627 info, err := context.ImportDir(dir, 0)
628 if err != nil {
629 if _, nogo := err.(*build.NoGoError); nogo {
630 return nil, err
631 }
632 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
633 }
634
635
636 if usePkgCache {
637 if _, ok := pkgTags[dir]; !ok {
638 pkgTags[dir] = info.AllTags
639 key = tagKey(dir, context, info.AllTags)
640 }
641 }
642
643 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
644
645
646 var files []*ast.File
647 for _, file := range filenames {
648 f, err := w.parseFile(dir, file)
649 if err != nil {
650 log.Fatalf("error parsing package %s: %s", name, err)
651 }
652 files = append(files, f)
653 }
654
655
656 conf := types.Config{
657 IgnoreFuncBodies: true,
658 FakeImportC: true,
659 Importer: w,
660 }
661 pkg, err = conf.Check(name, fset, files, nil)
662 if err != nil {
663 ctxt := "<no context>"
664 if w.context != nil {
665 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
666 }
667 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
668 }
669
670 if usePkgCache {
671 pkgCache[key] = pkg
672 }
673
674 w.imported[name] = pkg
675 return pkg, nil
676 }
677
678
679
680
681 func (w *Walker) pushScope(name string) (popFunc func()) {
682 w.scope = append(w.scope, name)
683 return func() {
684 if len(w.scope) == 0 {
685 log.Fatalf("attempt to leave scope %q with empty scope list", name)
686 }
687 if w.scope[len(w.scope)-1] != name {
688 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
689 }
690 w.scope = w.scope[:len(w.scope)-1]
691 }
692 }
693
694 func sortedMethodNames(typ *types.Interface) []string {
695 n := typ.NumMethods()
696 list := make([]string, n)
697 for i := range list {
698 list[i] = typ.Method(i).Name()
699 }
700 sort.Strings(list)
701 return list
702 }
703
704 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
705 switch typ := typ.(type) {
706 case *types.Basic:
707 s := typ.Name()
708 switch typ.Kind() {
709 case types.UnsafePointer:
710 s = "unsafe.Pointer"
711 case types.UntypedBool:
712 s = "ideal-bool"
713 case types.UntypedInt:
714 s = "ideal-int"
715 case types.UntypedRune:
716
717
718 s = "ideal-char"
719 case types.UntypedFloat:
720 s = "ideal-float"
721 case types.UntypedComplex:
722 s = "ideal-complex"
723 case types.UntypedString:
724 s = "ideal-string"
725 case types.UntypedNil:
726 panic("should never see untyped nil type")
727 default:
728 switch s {
729 case "byte":
730 s = "uint8"
731 case "rune":
732 s = "int32"
733 }
734 }
735 buf.WriteString(s)
736
737 case *types.Array:
738 fmt.Fprintf(buf, "[%d]", typ.Len())
739 w.writeType(buf, typ.Elem())
740
741 case *types.Slice:
742 buf.WriteString("[]")
743 w.writeType(buf, typ.Elem())
744
745 case *types.Struct:
746 buf.WriteString("struct")
747
748 case *types.Pointer:
749 buf.WriteByte('*')
750 w.writeType(buf, typ.Elem())
751
752 case *types.Tuple:
753 panic("should never see a tuple type")
754
755 case *types.Signature:
756 buf.WriteString("func")
757 w.writeSignature(buf, typ)
758
759 case *types.Interface:
760 buf.WriteString("interface{")
761 if typ.NumMethods() > 0 {
762 buf.WriteByte(' ')
763 buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
764 buf.WriteByte(' ')
765 }
766 buf.WriteString("}")
767
768 case *types.Map:
769 buf.WriteString("map[")
770 w.writeType(buf, typ.Key())
771 buf.WriteByte(']')
772 w.writeType(buf, typ.Elem())
773
774 case *types.Chan:
775 var s string
776 switch typ.Dir() {
777 case types.SendOnly:
778 s = "chan<- "
779 case types.RecvOnly:
780 s = "<-chan "
781 case types.SendRecv:
782 s = "chan "
783 default:
784 panic("unreachable")
785 }
786 buf.WriteString(s)
787 w.writeType(buf, typ.Elem())
788
789 case *types.Named:
790 obj := typ.Obj()
791 pkg := obj.Pkg()
792 if pkg != nil && pkg != w.current {
793 buf.WriteString(pkg.Name())
794 buf.WriteByte('.')
795 }
796 buf.WriteString(typ.Obj().Name())
797
798 default:
799 panic(fmt.Sprintf("unknown type %T", typ))
800 }
801 }
802
803 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
804 w.writeParams(buf, sig.Params(), sig.Variadic())
805 switch res := sig.Results(); res.Len() {
806 case 0:
807
808 case 1:
809 buf.WriteByte(' ')
810 w.writeType(buf, res.At(0).Type())
811 default:
812 buf.WriteByte(' ')
813 w.writeParams(buf, res, false)
814 }
815 }
816
817 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
818 buf.WriteByte('(')
819 for i, n := 0, t.Len(); i < n; i++ {
820 if i > 0 {
821 buf.WriteString(", ")
822 }
823 typ := t.At(i).Type()
824 if variadic && i+1 == n {
825 buf.WriteString("...")
826 typ = typ.(*types.Slice).Elem()
827 }
828 w.writeType(buf, typ)
829 }
830 buf.WriteByte(')')
831 }
832
833 func (w *Walker) typeString(typ types.Type) string {
834 var buf bytes.Buffer
835 w.writeType(&buf, typ)
836 return buf.String()
837 }
838
839 func (w *Walker) signatureString(sig *types.Signature) string {
840 var buf bytes.Buffer
841 w.writeSignature(&buf, sig)
842 return buf.String()
843 }
844
845 func (w *Walker) emitObj(obj types.Object) {
846 switch obj := obj.(type) {
847 case *types.Const:
848 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
849 x := obj.Val()
850 short := x.String()
851 exact := x.ExactString()
852 if short == exact {
853 w.emitf("const %s = %s", obj.Name(), short)
854 } else {
855 w.emitf("const %s = %s // %s", obj.Name(), short, exact)
856 }
857 case *types.Var:
858 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
859 case *types.TypeName:
860 w.emitType(obj)
861 case *types.Func:
862 w.emitFunc(obj)
863 default:
864 panic("unknown object: " + obj.String())
865 }
866 }
867
868 func (w *Walker) emitType(obj *types.TypeName) {
869 name := obj.Name()
870 typ := obj.Type()
871 if obj.IsAlias() {
872 w.emitf("type %s = %s", name, w.typeString(typ))
873 return
874 }
875 switch typ := typ.Underlying().(type) {
876 case *types.Struct:
877 w.emitStructType(name, typ)
878 case *types.Interface:
879 w.emitIfaceType(name, typ)
880 return
881 default:
882 w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
883 }
884
885
886 var methodNames map[string]bool
887 vset := types.NewMethodSet(typ)
888 for i, n := 0, vset.Len(); i < n; i++ {
889 m := vset.At(i)
890 if m.Obj().Exported() {
891 w.emitMethod(m)
892 if methodNames == nil {
893 methodNames = make(map[string]bool)
894 }
895 methodNames[m.Obj().Name()] = true
896 }
897 }
898
899
900
901
902 pset := types.NewMethodSet(types.NewPointer(typ))
903 for i, n := 0, pset.Len(); i < n; i++ {
904 m := pset.At(i)
905 if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
906 w.emitMethod(m)
907 }
908 }
909 }
910
911 func (w *Walker) emitStructType(name string, typ *types.Struct) {
912 typeStruct := fmt.Sprintf("type %s struct", name)
913 w.emitf(typeStruct)
914 defer w.pushScope(typeStruct)()
915
916 for i := 0; i < typ.NumFields(); i++ {
917 f := typ.Field(i)
918 if !f.Exported() {
919 continue
920 }
921 typ := f.Type()
922 if f.Anonymous() {
923 w.emitf("embedded %s", w.typeString(typ))
924 continue
925 }
926 w.emitf("%s %s", f.Name(), w.typeString(typ))
927 }
928 }
929
930 func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
931 pop := w.pushScope("type " + name + " interface")
932
933 var methodNames []string
934 complete := true
935 mset := types.NewMethodSet(typ)
936 for i, n := 0, mset.Len(); i < n; i++ {
937 m := mset.At(i).Obj().(*types.Func)
938 if !m.Exported() {
939 complete = false
940 continue
941 }
942 methodNames = append(methodNames, m.Name())
943 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
944 }
945
946 if !complete {
947
948
949
950
951
952
953
954 w.emitf("unexported methods")
955 }
956
957 pop()
958
959 if !complete {
960 return
961 }
962
963 if len(methodNames) == 0 {
964 w.emitf("type %s interface {}", name)
965 return
966 }
967
968 sort.Strings(methodNames)
969 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
970 }
971
972 func (w *Walker) emitFunc(f *types.Func) {
973 sig := f.Type().(*types.Signature)
974 if sig.Recv() != nil {
975 panic("method considered a regular function: " + f.String())
976 }
977 w.emitf("func %s%s", f.Name(), w.signatureString(sig))
978 }
979
980 func (w *Walker) emitMethod(m *types.Selection) {
981 sig := m.Type().(*types.Signature)
982 recv := sig.Recv().Type()
983
984 if true {
985 base := recv
986 if p, _ := recv.(*types.Pointer); p != nil {
987 base = p.Elem()
988 }
989 if obj := base.(*types.Named).Obj(); !obj.Exported() {
990 log.Fatalf("exported method with unexported receiver base type: %s", m)
991 }
992 }
993 w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig))
994 }
995
996 func (w *Walker) emitf(format string, args ...interface{}) {
997 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
998 if strings.Contains(f, "\n") {
999 panic("feature contains newlines: " + f)
1000 }
1001
1002 if _, dup := w.features[f]; dup {
1003 panic("duplicate feature inserted: " + f)
1004 }
1005 w.features[f] = true
1006
1007 if *verbose {
1008 log.Printf("feature: %s", f)
1009 }
1010 }
1011
View as plain text