1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// This package computes the exported API of a set of Go packages. 6// It is only a test, not a command, nor a usefully importable package. 7 8package main 9 10import ( 11 "bufio" 12 "bytes" 13 "encoding/json" 14 "fmt" 15 "go/ast" 16 "go/build" 17 "go/parser" 18 "go/token" 19 "go/types" 20 "internal/testenv" 21 "io" 22 "log" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "sort" 29 "strconv" 30 "strings" 31 "sync" 32 "testing" 33) 34 35const verbose = false 36 37func goCmd() string { 38 var exeSuffix string 39 if runtime.GOOS == "windows" { 40 exeSuffix = ".exe" 41 } 42 path := filepath.Join(testenv.GOROOT(nil), "bin", "go"+exeSuffix) 43 if _, err := os.Stat(path); err == nil { 44 return path 45 } 46 return "go" 47} 48 49// contexts are the default contexts which are scanned. 50var contexts = []*build.Context{ 51 {GOOS: "linux", GOARCH: "386", CgoEnabled: true}, 52 {GOOS: "linux", GOARCH: "386"}, 53 {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}, 54 {GOOS: "linux", GOARCH: "amd64"}, 55 {GOOS: "linux", GOARCH: "arm", CgoEnabled: true}, 56 {GOOS: "linux", GOARCH: "arm"}, 57 {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true}, 58 {GOOS: "darwin", GOARCH: "amd64"}, 59 {GOOS: "darwin", GOARCH: "arm64", CgoEnabled: true}, 60 {GOOS: "darwin", GOARCH: "arm64"}, 61 {GOOS: "windows", GOARCH: "amd64"}, 62 {GOOS: "windows", GOARCH: "386"}, 63 {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true}, 64 {GOOS: "freebsd", GOARCH: "386"}, 65 {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true}, 66 {GOOS: "freebsd", GOARCH: "amd64"}, 67 {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true}, 68 {GOOS: "freebsd", GOARCH: "arm"}, 69 {GOOS: "freebsd", GOARCH: "arm64", CgoEnabled: true}, 70 {GOOS: "freebsd", GOARCH: "arm64"}, 71 {GOOS: "freebsd", GOARCH: "riscv64", CgoEnabled: true}, 72 {GOOS: "freebsd", GOARCH: "riscv64"}, 73 {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true}, 74 {GOOS: "netbsd", GOARCH: "386"}, 75 {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true}, 76 {GOOS: "netbsd", GOARCH: "amd64"}, 77 {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true}, 78 {GOOS: "netbsd", GOARCH: "arm"}, 79 {GOOS: "netbsd", GOARCH: "arm64", CgoEnabled: true}, 80 {GOOS: "netbsd", GOARCH: "arm64"}, 81 {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true}, 82 {GOOS: "openbsd", GOARCH: "386"}, 83 {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true}, 84 {GOOS: "openbsd", GOARCH: "amd64"}, 85} 86 87func contextName(c *build.Context) string { 88 s := c.GOOS + "-" + c.GOARCH 89 if c.CgoEnabled { 90 s += "-cgo" 91 } 92 if c.Dir != "" { 93 s += fmt.Sprintf(" [%s]", c.Dir) 94 } 95 return s 96} 97 98var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`) 99 100var exitCode = 0 101 102func Check(t *testing.T) { 103 checkFiles, err := filepath.Glob(filepath.Join(testenv.GOROOT(t), "api/go1*.txt")) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 var nextFiles []string 109 if v := runtime.Version(); strings.Contains(v, "devel") || strings.Contains(v, "beta") { 110 next, err := filepath.Glob(filepath.Join(testenv.GOROOT(t), "api/next/*.txt")) 111 if err != nil { 112 t.Fatal(err) 113 } 114 nextFiles = next 115 } 116 117 for _, c := range contexts { 118 c.Compiler = build.Default.Compiler 119 } 120 121 walkers := make([]*Walker, len(contexts)) 122 var wg sync.WaitGroup 123 for i, context := range contexts { 124 i, context := i, context 125 wg.Add(1) 126 go func() { 127 defer wg.Done() 128 walkers[i] = NewWalker(context, filepath.Join(testenv.GOROOT(t), "src")) 129 }() 130 } 131 wg.Wait() 132 133 var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true 134 for _, w := range walkers { 135 for _, name := range w.stdPackages { 136 pkg, err := w.import_(name) 137 if _, nogo := err.(*build.NoGoError); nogo { 138 continue 139 } 140 if err != nil { 141 log.Fatalf("Import(%q): %v", name, err) 142 } 143 w.export(pkg) 144 } 145 146 ctxName := contextName(w.context) 147 for _, f := range w.Features() { 148 if featureCtx[f] == nil { 149 featureCtx[f] = make(map[string]bool) 150 } 151 featureCtx[f][ctxName] = true 152 } 153 } 154 155 var features []string 156 for f, cmap := range featureCtx { 157 if len(cmap) == len(contexts) { 158 features = append(features, f) 159 continue 160 } 161 comma := strings.Index(f, ",") 162 for cname := range cmap { 163 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:]) 164 features = append(features, f2) 165 } 166 } 167 168 bw := bufio.NewWriter(os.Stdout) 169 defer bw.Flush() 170 171 var required []string 172 for _, file := range checkFiles { 173 required = append(required, fileFeatures(file, needApproval(file))...) 174 } 175 for _, file := range nextFiles { 176 required = append(required, fileFeatures(file, true)...) 177 } 178 exception := fileFeatures(filepath.Join(testenv.GOROOT(t), "api/except.txt"), false) 179 180 if exitCode == 1 { 181 t.Errorf("API database problems found") 182 } 183 if !compareAPI(bw, features, required, exception) { 184 t.Errorf("API differences found") 185 } 186} 187 188// export emits the exported package features. 189func (w *Walker) export(pkg *apiPackage) { 190 if verbose { 191 log.Println(pkg) 192 } 193 pop := w.pushScope("pkg " + pkg.Path()) 194 w.current = pkg 195 w.collectDeprecated() 196 scope := pkg.Scope() 197 for _, name := range scope.Names() { 198 if token.IsExported(name) { 199 w.emitObj(scope.Lookup(name)) 200 } 201 } 202 pop() 203} 204 205func set(items []string) map[string]bool { 206 s := make(map[string]bool) 207 for _, v := range items { 208 s[v] = true 209 } 210 return s 211} 212 213var spaceParensRx = regexp.MustCompile(` \(\S+?\)`) 214 215func featureWithoutContext(f string) string { 216 if !strings.Contains(f, "(") { 217 return f 218 } 219 return spaceParensRx.ReplaceAllString(f, "") 220} 221 222// portRemoved reports whether the given port-specific API feature is 223// okay to no longer exist because its port was removed. 224func portRemoved(feature string) bool { 225 return strings.Contains(feature, "(darwin-386)") || 226 strings.Contains(feature, "(darwin-386-cgo)") 227} 228 229func compareAPI(w io.Writer, features, required, exception []string) (ok bool) { 230 ok = true 231 232 featureSet := set(features) 233 exceptionSet := set(exception) 234 235 sort.Strings(features) 236 sort.Strings(required) 237 238 take := func(sl *[]string) string { 239 s := (*sl)[0] 240 *sl = (*sl)[1:] 241 return s 242 } 243 244 for len(features) > 0 || len(required) > 0 { 245 switch { 246 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]): 247 feature := take(&required) 248 if exceptionSet[feature] { 249 // An "unfortunate" case: the feature was once 250 // included in the API (e.g. go1.txt), but was 251 // subsequently removed. These are already 252 // acknowledged by being in the file 253 // "api/except.txt". No need to print them out 254 // here. 255 } else if portRemoved(feature) { 256 // okay. 257 } else if featureSet[featureWithoutContext(feature)] { 258 // okay. 259 } else { 260 fmt.Fprintf(w, "-%s\n", feature) 261 ok = false // broke compatibility 262 } 263 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]): 264 newFeature := take(&features) 265 fmt.Fprintf(w, "+%s\n", newFeature) 266 ok = false // feature not in api/next/* 267 default: 268 take(&required) 269 take(&features) 270 } 271 } 272 273 return ok 274} 275 276// aliasReplacer applies type aliases to earlier API files, 277// to avoid misleading negative results. 278// This makes all the references to os.FileInfo in go1.txt 279// be read as if they said fs.FileInfo, since os.FileInfo is now an alias. 280// If there are many of these, we could do a more general solution, 281// but for now the replacer is fine. 282var aliasReplacer = strings.NewReplacer( 283 "os.FileInfo", "fs.FileInfo", 284 "os.FileMode", "fs.FileMode", 285 "os.PathError", "fs.PathError", 286) 287 288func fileFeatures(filename string, needApproval bool) []string { 289 bs, err := os.ReadFile(filename) 290 if err != nil { 291 log.Fatal(err) 292 } 293 s := string(bs) 294 295 // Diagnose common mistakes people make, 296 // since there is no apifmt to format these files. 297 // The missing final newline is important for the 298 // final release step of cat next/*.txt >go1.X.txt. 299 // If the files don't end in full lines, the concatenation goes awry. 300 if strings.Contains(s, "\r") { 301 log.Printf("%s: contains CRLFs", filename) 302 exitCode = 1 303 } 304 if filepath.Base(filename) == "go1.4.txt" { 305 // No use for blank lines in api files, except go1.4.txt 306 // used them in a reasonable way and we should let it be. 307 } else if strings.HasPrefix(s, "\n") || strings.Contains(s, "\n\n") { 308 log.Printf("%s: contains a blank line", filename) 309 exitCode = 1 310 } 311 if s == "" { 312 log.Printf("%s: empty file", filename) 313 exitCode = 1 314 } else if s[len(s)-1] != '\n' { 315 log.Printf("%s: missing final newline", filename) 316 exitCode = 1 317 } 318 s = aliasReplacer.Replace(s) 319 lines := strings.Split(s, "\n") 320 var nonblank []string 321 for i, line := range lines { 322 line = strings.TrimSpace(line) 323 if line == "" || strings.HasPrefix(line, "#") { 324 continue 325 } 326 if needApproval { 327 feature, approval, ok := strings.Cut(line, "#") 328 if !ok { 329 log.Printf("%s:%d: missing proposal approval\n", filename, i+1) 330 exitCode = 1 331 } else { 332 _, err := strconv.Atoi(approval) 333 if err != nil { 334 log.Printf("%s:%d: malformed proposal approval #%s\n", filename, i+1, approval) 335 exitCode = 1 336 } 337 } 338 line = strings.TrimSpace(feature) 339 } else { 340 if strings.Contains(line, " #") { 341 log.Printf("%s:%d: unexpected approval\n", filename, i+1) 342 exitCode = 1 343 } 344 } 345 nonblank = append(nonblank, line) 346 } 347 return nonblank 348} 349 350var fset = token.NewFileSet() 351 352type Walker struct { 353 context *build.Context 354 root string 355 scope []string 356 current *apiPackage 357 deprecated map[token.Pos]bool 358 features map[string]bool // set 359 imported map[string]*apiPackage // packages already imported 360 stdPackages []string // names, omitting "unsafe", internal, and vendored packages 361 importMap map[string]map[string]string // importer dir -> import path -> canonical path 362 importDir map[string]string // canonical import path -> dir 363 364} 365 366func NewWalker(context *build.Context, root string) *Walker { 367 w := &Walker{ 368 context: context, 369 root: root, 370 features: map[string]bool{}, 371 imported: map[string]*apiPackage{"unsafe": &apiPackage{Package: types.Unsafe}}, 372 } 373 w.loadImports() 374 return w 375} 376 377func (w *Walker) Features() (fs []string) { 378 for f := range w.features { 379 fs = append(fs, f) 380 } 381 sort.Strings(fs) 382 return 383} 384 385var parsedFileCache = make(map[string]*ast.File) 386 387func (w *Walker) parseFile(dir, file string) (*ast.File, error) { 388 filename := filepath.Join(dir, file) 389 if f := parsedFileCache[filename]; f != nil { 390 return f, nil 391 } 392 393 f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) 394 if err != nil { 395 return nil, err 396 } 397 parsedFileCache[filename] = f 398 399 return f, nil 400} 401 402// Disable before debugging non-obvious errors from the type-checker. 403const usePkgCache = true 404 405var ( 406 pkgCache = map[string]*apiPackage{} // map tagKey to package 407 pkgTags = map[string][]string{} // map import dir to list of relevant tags 408) 409 410// tagKey returns the tag-based key to use in the pkgCache. 411// It is a comma-separated string; the first part is dir, the rest tags. 412// The satisfied tags are derived from context but only those that 413// matter (the ones listed in the tags argument plus GOOS and GOARCH) are used. 414// The tags list, which came from go/build's Package.AllTags, 415// is known to be sorted. 416func tagKey(dir string, context *build.Context, tags []string) string { 417 ctags := map[string]bool{ 418 context.GOOS: true, 419 context.GOARCH: true, 420 } 421 if context.CgoEnabled { 422 ctags["cgo"] = true 423 } 424 for _, tag := range context.BuildTags { 425 ctags[tag] = true 426 } 427 // TODO: ReleaseTags (need to load default) 428 key := dir 429 430 // explicit on GOOS and GOARCH as global cache will use "all" cached packages for 431 // an indirect imported package. See https://github.com/golang/go/issues/21181 432 // for more detail. 433 tags = append(tags, context.GOOS, context.GOARCH) 434 sort.Strings(tags) 435 436 for _, tag := range tags { 437 if ctags[tag] { 438 key += "," + tag 439 ctags[tag] = false 440 } 441 } 442 return key 443} 444 445type listImports struct { 446 stdPackages []string // names, omitting "unsafe", internal, and vendored packages 447 importDir map[string]string // canonical import path → directory 448 importMap map[string]map[string]string // import path → canonical import path 449} 450 451var listCache sync.Map // map[string]listImports, keyed by contextName 452 453// listSem is a semaphore restricting concurrent invocations of 'go list'. 'go 454// list' has its own internal concurrency, so we use a hard-coded constant (to 455// allow the I/O-intensive phases of 'go list' to overlap) instead of scaling 456// all the way up to GOMAXPROCS. 457var listSem = make(chan semToken, 2) 458 459type semToken struct{} 460 461// loadImports populates w with information about the packages in the standard 462// library and the packages they themselves import in w's build context. 463// 464// The source import path and expanded import path are identical except for vendored packages. 465// For example, on return: 466// 467// w.importMap["math"] = "math" 468// w.importDir["math"] = "<goroot>/src/math" 469// 470// w.importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route" 471// w.importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route" 472// 473// Since the set of packages that exist depends on context, the result of 474// loadImports also depends on context. However, to improve test running time 475// the configuration for each environment is cached across runs. 476func (w *Walker) loadImports() { 477 if w.context == nil { 478 return // test-only Walker; does not use the import map 479 } 480 481 name := contextName(w.context) 482 483 imports, ok := listCache.Load(name) 484 if !ok { 485 listSem <- semToken{} 486 defer func() { <-listSem }() 487 488 cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std") 489 cmd.Env = listEnv(w.context) 490 if w.context.Dir != "" { 491 cmd.Dir = w.context.Dir 492 } 493 cmd.Stderr = os.Stderr 494 out, err := cmd.Output() 495 if err != nil { 496 log.Fatalf("loading imports: %v\n%s", err, out) 497 } 498 499 var stdPackages []string 500 importMap := make(map[string]map[string]string) 501 importDir := make(map[string]string) 502 dec := json.NewDecoder(bytes.NewReader(out)) 503 for { 504 var pkg struct { 505 ImportPath, Dir string 506 ImportMap map[string]string 507 Standard bool 508 } 509 err := dec.Decode(&pkg) 510 if err == io.EOF { 511 break 512 } 513 if err != nil { 514 log.Fatalf("go list: invalid output: %v", err) 515 } 516 517 // - Package "unsafe" contains special signatures requiring 518 // extra care when printing them - ignore since it is not 519 // going to change w/o a language change. 520 // - Internal and vendored packages do not contribute to our 521 // API surface. (If we are running within the "std" module, 522 // vendored dependencies appear as themselves instead of 523 // their "vendor/" standard-library copies.) 524 // - 'go list std' does not include commands, which cannot be 525 // imported anyway. 526 if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) { 527 stdPackages = append(stdPackages, ip) 528 } 529 importDir[pkg.ImportPath] = pkg.Dir 530 if len(pkg.ImportMap) > 0 { 531 importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap)) 532 } 533 for k, v := range pkg.ImportMap { 534 importMap[pkg.Dir][k] = v 535 } 536 } 537 538 sort.Strings(stdPackages) 539 imports = listImports{ 540 stdPackages: stdPackages, 541 importMap: importMap, 542 importDir: importDir, 543 } 544 imports, _ = listCache.LoadOrStore(name, imports) 545 } 546 547 li := imports.(listImports) 548 w.stdPackages = li.stdPackages 549 w.importDir = li.importDir 550 w.importMap = li.importMap 551} 552 553// listEnv returns the process environment to use when invoking 'go list' for 554// the given context. 555func listEnv(c *build.Context) []string { 556 if c == nil { 557 return os.Environ() 558 } 559 560 environ := append(os.Environ(), 561 "GOOS="+c.GOOS, 562 "GOARCH="+c.GOARCH) 563 if c.CgoEnabled { 564 environ = append(environ, "CGO_ENABLED=1") 565 } else { 566 environ = append(environ, "CGO_ENABLED=0") 567 } 568 return environ 569} 570 571type apiPackage struct { 572 *types.Package 573 Files []*ast.File 574} 575 576// Importing is a sentinel taking the place in Walker.imported 577// for a package that is in the process of being imported. 578var importing apiPackage 579 580// Import implements types.Importer. 581func (w *Walker) Import(name string) (*types.Package, error) { 582 return w.ImportFrom(name, "", 0) 583} 584 585// ImportFrom implements types.ImporterFrom. 586func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) { 587 pkg, err := w.importFrom(fromPath, fromDir, mode) 588 if err != nil { 589 return nil, err 590 } 591 return pkg.Package, nil 592} 593 594func (w *Walker) import_(name string) (*apiPackage, error) { 595 return w.importFrom(name, "", 0) 596} 597 598func (w *Walker) importFrom(fromPath, fromDir string, mode types.ImportMode) (*apiPackage, error) { 599 name := fromPath 600 if canonical, ok := w.importMap[fromDir][fromPath]; ok { 601 name = canonical 602 } 603 604 pkg := w.imported[name] 605 if pkg != nil { 606 if pkg == &importing { 607 log.Fatalf("cycle importing package %q", name) 608 } 609 return pkg, nil 610 } 611 w.imported[name] = &importing 612 613 // Determine package files. 614 dir := w.importDir[name] 615 if dir == "" { 616 dir = filepath.Join(w.root, filepath.FromSlash(name)) 617 } 618 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() { 619 log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err) 620 } 621 622 context := w.context 623 if context == nil { 624 context = &build.Default 625 } 626 627 // Look in cache. 628 // If we've already done an import with the same set 629 // of relevant tags, reuse the result. 630 var key string 631 if usePkgCache { 632 if tags, ok := pkgTags[dir]; ok { 633 key = tagKey(dir, context, tags) 634 if pkg := pkgCache[key]; pkg != nil { 635 w.imported[name] = pkg 636 return pkg, nil 637 } 638 } 639 } 640 641 info, err := context.ImportDir(dir, 0) 642 if err != nil { 643 if _, nogo := err.(*build.NoGoError); nogo { 644 return nil, err 645 } 646 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) 647 } 648 649 // Save tags list first time we see a directory. 650 if usePkgCache { 651 if _, ok := pkgTags[dir]; !ok { 652 pkgTags[dir] = info.AllTags 653 key = tagKey(dir, context, info.AllTags) 654 } 655 } 656 657 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...) 658 659 // Parse package files. 660 var files []*ast.File 661 for _, file := range filenames { 662 f, err := w.parseFile(dir, file) 663 if err != nil { 664 log.Fatalf("error parsing package %s: %s", name, err) 665 } 666 files = append(files, f) 667 } 668 669 // Type-check package files. 670 var sizes types.Sizes 671 if w.context != nil { 672 sizes = types.SizesFor(w.context.Compiler, w.context.GOARCH) 673 } 674 conf := types.Config{ 675 IgnoreFuncBodies: true, 676 FakeImportC: true, 677 Importer: w, 678 Sizes: sizes, 679 } 680 tpkg, err := conf.Check(name, fset, files, nil) 681 if err != nil { 682 ctxt := "<no context>" 683 if w.context != nil { 684 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH) 685 } 686 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt) 687 } 688 pkg = &apiPackage{tpkg, files} 689 690 if usePkgCache { 691 pkgCache[key] = pkg 692 } 693 694 w.imported[name] = pkg 695 return pkg, nil 696} 697 698// pushScope enters a new scope (walking a package, type, node, etc) 699// and returns a function that will leave the scope (with sanity checking 700// for mismatched pushes & pops) 701func (w *Walker) pushScope(name string) (popFunc func()) { 702 w.scope = append(w.scope, name) 703 return func() { 704 if len(w.scope) == 0 { 705 log.Fatalf("attempt to leave scope %q with empty scope list", name) 706 } 707 if w.scope[len(w.scope)-1] != name { 708 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope) 709 } 710 w.scope = w.scope[:len(w.scope)-1] 711 } 712} 713 714func sortedMethodNames(typ *types.Interface) []string { 715 n := typ.NumMethods() 716 list := make([]string, n) 717 for i := range list { 718 list[i] = typ.Method(i).Name() 719 } 720 sort.Strings(list) 721 return list 722} 723 724// sortedEmbeddeds returns constraint types embedded in an 725// interface. It does not include embedded interface types or methods. 726func (w *Walker) sortedEmbeddeds(typ *types.Interface) []string { 727 n := typ.NumEmbeddeds() 728 list := make([]string, 0, n) 729 for i := 0; i < n; i++ { 730 emb := typ.EmbeddedType(i) 731 switch emb := emb.(type) { 732 case *types.Interface: 733 list = append(list, w.sortedEmbeddeds(emb)...) 734 case *types.Union: 735 var buf bytes.Buffer 736 nu := emb.Len() 737 for i := 0; i < nu; i++ { 738 if i > 0 { 739 buf.WriteString(" | ") 740 } 741 term := emb.Term(i) 742 if term.Tilde() { 743 buf.WriteByte('~') 744 } 745 w.writeType(&buf, term.Type()) 746 } 747 list = append(list, buf.String()) 748 } 749 } 750 sort.Strings(list) 751 return list 752} 753 754func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { 755 switch typ := typ.(type) { 756 case *types.Basic: 757 s := typ.Name() 758 switch typ.Kind() { 759 case types.UnsafePointer: 760 s = "unsafe.Pointer" 761 case types.UntypedBool: 762 s = "ideal-bool" 763 case types.UntypedInt: 764 s = "ideal-int" 765 case types.UntypedRune: 766 // "ideal-char" for compatibility with old tool 767 // TODO(gri) change to "ideal-rune" 768 s = "ideal-char" 769 case types.UntypedFloat: 770 s = "ideal-float" 771 case types.UntypedComplex: 772 s = "ideal-complex" 773 case types.UntypedString: 774 s = "ideal-string" 775 case types.UntypedNil: 776 panic("should never see untyped nil type") 777 default: 778 switch s { 779 case "byte": 780 s = "uint8" 781 case "rune": 782 s = "int32" 783 } 784 } 785 buf.WriteString(s) 786 787 case *types.Array: 788 fmt.Fprintf(buf, "[%d]", typ.Len()) 789 w.writeType(buf, typ.Elem()) 790 791 case *types.Slice: 792 buf.WriteString("[]") 793 w.writeType(buf, typ.Elem()) 794 795 case *types.Struct: 796 buf.WriteString("struct") 797 798 case *types.Pointer: 799 buf.WriteByte('*') 800 w.writeType(buf, typ.Elem()) 801 802 case *types.Tuple: 803 panic("should never see a tuple type") 804 805 case *types.Signature: 806 buf.WriteString("func") 807 w.writeSignature(buf, typ) 808 809 case *types.Interface: 810 buf.WriteString("interface{") 811 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 { 812 buf.WriteByte(' ') 813 } 814 if typ.NumMethods() > 0 { 815 buf.WriteString(strings.Join(sortedMethodNames(typ), ", ")) 816 } 817 if typ.NumEmbeddeds() > 0 { 818 buf.WriteString(strings.Join(w.sortedEmbeddeds(typ), ", ")) 819 } 820 if typ.NumMethods() > 0 || typ.NumEmbeddeds() > 0 { 821 buf.WriteByte(' ') 822 } 823 buf.WriteString("}") 824 825 case *types.Map: 826 buf.WriteString("map[") 827 w.writeType(buf, typ.Key()) 828 buf.WriteByte(']') 829 w.writeType(buf, typ.Elem()) 830 831 case *types.Chan: 832 var s string 833 switch typ.Dir() { 834 case types.SendOnly: 835 s = "chan<- " 836 case types.RecvOnly: 837 s = "<-chan " 838 case types.SendRecv: 839 s = "chan " 840 default: 841 panic("unreachable") 842 } 843 buf.WriteString(s) 844 w.writeType(buf, typ.Elem()) 845 846 case *types.Alias: 847 w.writeType(buf, types.Unalias(typ)) 848 849 case *types.Named: 850 obj := typ.Obj() 851 pkg := obj.Pkg() 852 if pkg != nil && pkg != w.current.Package { 853 buf.WriteString(pkg.Name()) 854 buf.WriteByte('.') 855 } 856 buf.WriteString(typ.Obj().Name()) 857 if targs := typ.TypeArgs(); targs.Len() > 0 { 858 buf.WriteByte('[') 859 for i := 0; i < targs.Len(); i++ { 860 if i > 0 { 861 buf.WriteString(", ") 862 } 863 w.writeType(buf, targs.At(i)) 864 } 865 buf.WriteByte(']') 866 } 867 868 case *types.TypeParam: 869 // Type parameter names may change, so use a placeholder instead. 870 fmt.Fprintf(buf, "$%d", typ.Index()) 871 872 default: 873 panic(fmt.Sprintf("unknown type %T", typ)) 874 } 875} 876 877func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) { 878 if tparams := sig.TypeParams(); tparams != nil { 879 w.writeTypeParams(buf, tparams, true) 880 } 881 w.writeParams(buf, sig.Params(), sig.Variadic()) 882 switch res := sig.Results(); res.Len() { 883 case 0: 884 // nothing to do 885 case 1: 886 buf.WriteByte(' ') 887 w.writeType(buf, res.At(0).Type()) 888 default: 889 buf.WriteByte(' ') 890 w.writeParams(buf, res, false) 891 } 892} 893 894func (w *Walker) writeTypeParams(buf *bytes.Buffer, tparams *types.TypeParamList, withConstraints bool) { 895 buf.WriteByte('[') 896 c := tparams.Len() 897 for i := 0; i < c; i++ { 898 if i > 0 { 899 buf.WriteString(", ") 900 } 901 tp := tparams.At(i) 902 w.writeType(buf, tp) 903 if withConstraints { 904 buf.WriteByte(' ') 905 w.writeType(buf, tp.Constraint()) 906 } 907 } 908 buf.WriteByte(']') 909} 910 911func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) { 912 buf.WriteByte('(') 913 for i, n := 0, t.Len(); i < n; i++ { 914 if i > 0 { 915 buf.WriteString(", ") 916 } 917 typ := t.At(i).Type() 918 if variadic && i+1 == n { 919 buf.WriteString("...") 920 typ = typ.(*types.Slice).Elem() 921 } 922 w.writeType(buf, typ) 923 } 924 buf.WriteByte(')') 925} 926 927func (w *Walker) typeString(typ types.Type) string { 928 var buf bytes.Buffer 929 w.writeType(&buf, typ) 930 return buf.String() 931} 932 933func (w *Walker) signatureString(sig *types.Signature) string { 934 var buf bytes.Buffer 935 w.writeSignature(&buf, sig) 936 return buf.String() 937} 938 939func (w *Walker) emitObj(obj types.Object) { 940 switch obj := obj.(type) { 941 case *types.Const: 942 if w.isDeprecated(obj) { 943 w.emitf("const %s //deprecated", obj.Name()) 944 } 945 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) 946 x := obj.Val() 947 short := x.String() 948 exact := x.ExactString() 949 if short == exact { 950 w.emitf("const %s = %s", obj.Name(), short) 951 } else { 952 w.emitf("const %s = %s // %s", obj.Name(), short, exact) 953 } 954 case *types.Var: 955 if w.isDeprecated(obj) { 956 w.emitf("var %s //deprecated", obj.Name()) 957 } 958 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) 959 case *types.TypeName: 960 w.emitType(obj) 961 case *types.Func: 962 w.emitFunc(obj) 963 default: 964 panic("unknown object: " + obj.String()) 965 } 966} 967 968func (w *Walker) emitType(obj *types.TypeName) { 969 name := obj.Name() 970 if w.isDeprecated(obj) { 971 w.emitf("type %s //deprecated", name) 972 } 973 typ := obj.Type() 974 if obj.IsAlias() { 975 w.emitf("type %s = %s", name, w.typeString(typ)) 976 return 977 } 978 if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil { 979 var buf bytes.Buffer 980 buf.WriteString(name) 981 w.writeTypeParams(&buf, tparams, true) 982 name = buf.String() 983 } 984 switch typ := typ.Underlying().(type) { 985 case *types.Struct: 986 w.emitStructType(name, typ) 987 case *types.Interface: 988 w.emitIfaceType(name, typ) 989 return // methods are handled by emitIfaceType 990 default: 991 w.emitf("type %s %s", name, w.typeString(typ.Underlying())) 992 } 993 994 // emit methods with value receiver 995 var methodNames map[string]bool 996 vset := types.NewMethodSet(typ) 997 for i, n := 0, vset.Len(); i < n; i++ { 998 m := vset.At(i) 999 if m.Obj().Exported() { 1000 w.emitMethod(m) 1001 if methodNames == nil { 1002 methodNames = make(map[string]bool) 1003 } 1004 methodNames[m.Obj().Name()] = true 1005 } 1006 } 1007 1008 // emit methods with pointer receiver; exclude 1009 // methods that we have emitted already 1010 // (the method set of *T includes the methods of T) 1011 pset := types.NewMethodSet(types.NewPointer(typ)) 1012 for i, n := 0, pset.Len(); i < n; i++ { 1013 m := pset.At(i) 1014 if m.Obj().Exported() && !methodNames[m.Obj().Name()] { 1015 w.emitMethod(m) 1016 } 1017 } 1018} 1019 1020func (w *Walker) emitStructType(name string, typ *types.Struct) { 1021 typeStruct := fmt.Sprintf("type %s struct", name) 1022 w.emitf(typeStruct) 1023 defer w.pushScope(typeStruct)() 1024 1025 for i := 0; i < typ.NumFields(); i++ { 1026 f := typ.Field(i) 1027 if !f.Exported() { 1028 continue 1029 } 1030 typ := f.Type() 1031 if f.Anonymous() { 1032 if w.isDeprecated(f) { 1033 w.emitf("embedded %s //deprecated", w.typeString(typ)) 1034 } 1035 w.emitf("embedded %s", w.typeString(typ)) 1036 continue 1037 } 1038 if w.isDeprecated(f) { 1039 w.emitf("%s //deprecated", f.Name()) 1040 } 1041 w.emitf("%s %s", f.Name(), w.typeString(typ)) 1042 } 1043} 1044 1045func (w *Walker) emitIfaceType(name string, typ *types.Interface) { 1046 pop := w.pushScope("type " + name + " interface") 1047 1048 var methodNames []string 1049 complete := true 1050 mset := types.NewMethodSet(typ) 1051 for i, n := 0, mset.Len(); i < n; i++ { 1052 m := mset.At(i).Obj().(*types.Func) 1053 if !m.Exported() { 1054 complete = false 1055 continue 1056 } 1057 methodNames = append(methodNames, m.Name()) 1058 if w.isDeprecated(m) { 1059 w.emitf("%s //deprecated", m.Name()) 1060 } 1061 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature))) 1062 } 1063 1064 if !complete { 1065 // The method set has unexported methods, so all the 1066 // implementations are provided by the same package, 1067 // so the method set can be extended. Instead of recording 1068 // the full set of names (below), record only that there were 1069 // unexported methods. (If the interface shrinks, we will notice 1070 // because a method signature emitted during the last loop 1071 // will disappear.) 1072 w.emitf("unexported methods") 1073 } 1074 1075 pop() 1076 1077 if !complete { 1078 return 1079 } 1080 1081 if len(methodNames) == 0 { 1082 w.emitf("type %s interface {}", name) 1083 return 1084 } 1085 1086 sort.Strings(methodNames) 1087 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", ")) 1088} 1089 1090func (w *Walker) emitFunc(f *types.Func) { 1091 sig := f.Type().(*types.Signature) 1092 if sig.Recv() != nil { 1093 panic("method considered a regular function: " + f.String()) 1094 } 1095 if w.isDeprecated(f) { 1096 w.emitf("func %s //deprecated", f.Name()) 1097 } 1098 w.emitf("func %s%s", f.Name(), w.signatureString(sig)) 1099} 1100 1101func (w *Walker) emitMethod(m *types.Selection) { 1102 sig := m.Type().(*types.Signature) 1103 recv := sig.Recv().Type() 1104 // report exported methods with unexported receiver base type 1105 if true { 1106 base := recv 1107 if p, _ := recv.(*types.Pointer); p != nil { 1108 base = p.Elem() 1109 } 1110 if obj := base.(*types.Named).Obj(); !obj.Exported() { 1111 log.Fatalf("exported method with unexported receiver base type: %s", m) 1112 } 1113 } 1114 tps := "" 1115 if rtp := sig.RecvTypeParams(); rtp != nil { 1116 var buf bytes.Buffer 1117 w.writeTypeParams(&buf, rtp, false) 1118 tps = buf.String() 1119 } 1120 if w.isDeprecated(m.Obj()) { 1121 w.emitf("method (%s%s) %s //deprecated", w.typeString(recv), tps, m.Obj().Name()) 1122 } 1123 w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig)) 1124} 1125 1126func (w *Walker) emitf(format string, args ...any) { 1127 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...) 1128 if strings.Contains(f, "\n") { 1129 panic("feature contains newlines: " + f) 1130 } 1131 1132 if _, dup := w.features[f]; dup { 1133 panic("duplicate feature inserted: " + f) 1134 } 1135 w.features[f] = true 1136 1137 if verbose { 1138 log.Printf("feature: %s", f) 1139 } 1140} 1141 1142func needApproval(filename string) bool { 1143 name := filepath.Base(filename) 1144 if name == "go1.txt" { 1145 return false 1146 } 1147 minor := strings.TrimSuffix(strings.TrimPrefix(name, "go1."), ".txt") 1148 n, err := strconv.Atoi(minor) 1149 if err != nil { 1150 log.Fatalf("unexpected api file: %v", name) 1151 } 1152 return n >= 19 // started tracking approvals in Go 1.19 1153} 1154 1155func (w *Walker) collectDeprecated() { 1156 isDeprecated := func(doc *ast.CommentGroup) bool { 1157 if doc != nil { 1158 for _, c := range doc.List { 1159 if strings.HasPrefix(c.Text, "// Deprecated:") { 1160 return true 1161 } 1162 } 1163 } 1164 return false 1165 } 1166 1167 w.deprecated = make(map[token.Pos]bool) 1168 mark := func(id *ast.Ident) { 1169 if id != nil { 1170 w.deprecated[id.Pos()] = true 1171 } 1172 } 1173 for _, file := range w.current.Files { 1174 ast.Inspect(file, func(n ast.Node) bool { 1175 switch n := n.(type) { 1176 case *ast.File: 1177 if isDeprecated(n.Doc) { 1178 mark(n.Name) 1179 } 1180 return true 1181 case *ast.GenDecl: 1182 if isDeprecated(n.Doc) { 1183 for _, spec := range n.Specs { 1184 switch spec := spec.(type) { 1185 case *ast.ValueSpec: 1186 for _, id := range spec.Names { 1187 mark(id) 1188 } 1189 case *ast.TypeSpec: 1190 mark(spec.Name) 1191 } 1192 } 1193 } 1194 return true // look at specs 1195 case *ast.FuncDecl: 1196 if isDeprecated(n.Doc) { 1197 mark(n.Name) 1198 } 1199 return false 1200 case *ast.TypeSpec: 1201 if isDeprecated(n.Doc) { 1202 mark(n.Name) 1203 } 1204 return true // recurse into struct or interface type 1205 case *ast.StructType: 1206 return true // recurse into fields 1207 case *ast.InterfaceType: 1208 return true // recurse into methods 1209 case *ast.FieldList: 1210 return true // recurse into fields 1211 case *ast.ValueSpec: 1212 if isDeprecated(n.Doc) { 1213 for _, id := range n.Names { 1214 mark(id) 1215 } 1216 } 1217 return false 1218 case *ast.Field: 1219 if isDeprecated(n.Doc) { 1220 for _, id := range n.Names { 1221 mark(id) 1222 } 1223 if len(n.Names) == 0 { 1224 // embedded field T or *T? 1225 typ := n.Type 1226 if ptr, ok := typ.(*ast.StarExpr); ok { 1227 typ = ptr.X 1228 } 1229 if id, ok := typ.(*ast.Ident); ok { 1230 mark(id) 1231 } 1232 } 1233 } 1234 return false 1235 default: 1236 return false 1237 } 1238 }) 1239 } 1240} 1241 1242func (w *Walker) isDeprecated(obj types.Object) bool { 1243 return w.deprecated[obj.Pos()] 1244} 1245