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