diff options
| author | Russ Cox <rsc@golang.org> | 2022-11-23 13:45:56 -0500 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2022-12-02 16:29:41 +0000 |
| commit | 1711f953e48c3de0f85726cb0299c0c0f0b928a1 (patch) | |
| tree | 270ac0747740ad7c5e016f417d36ed3e03a3e585 /src/cmd/api | |
| parent | dadd80ae204bda1c3a48245d8a938f55f71259ea (diff) | |
| download | go-1711f953e48c3de0f85726cb0299c0c0f0b928a1.tar.xz | |
cmd/api: track deprecations
Deprecating an API creates notices that go out to potentially
millions of Go developers encouraging them to update their code.
The choice to deprecate an API is as important as the choice to
add a new API. We should track those and make them explicit.
This will also ensure that deprecations go through proposal review.
Change-Id: Ide9f60c32e5a88fb133e0dfedd984b8b0f70f510
Reviewed-on: https://go-review.googlesource.com/c/go/+/453259
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/cmd/api')
| -rw-r--r-- | src/cmd/api/api.go | 170 | ||||
| -rw-r--r-- | src/cmd/api/api_test.go | 8 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p1/golden.txt | 8 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p1/p1.go | 10 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p2/golden.txt | 3 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p2/p2.go | 8 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p4/golden.txt | 1 | ||||
| -rw-r--r-- | src/cmd/api/testdata/src/pkg/p4/p4.go | 1 |
8 files changed, 186 insertions, 23 deletions
diff --git a/src/cmd/api/api.go b/src/cmd/api/api.go index f93e54cda1..b197b9c738 100644 --- a/src/cmd/api/api.go +++ b/src/cmd/api/api.go @@ -152,7 +152,7 @@ func Check(t *testing.T) { } for _, name := range pkgNames { - pkg, err := w.Import(name) + pkg, err := w.import_(name) if _, nogo := err.(*build.NoGoError); nogo { continue } @@ -206,12 +206,13 @@ func Check(t *testing.T) { } // export emits the exported package features. -func (w *Walker) export(pkg *types.Package) { +func (w *Walker) export(pkg *apiPackage) { if verbose { log.Println(pkg) } pop := w.pushScope("pkg " + pkg.Path()) w.current = pkg + w.collectDeprecated() scope := pkg.Scope() for _, name := range scope.Names() { if token.IsExported(name) { @@ -359,11 +360,12 @@ func fileFeatures(filename string, needApproval bool) []string { if !ok { log.Printf("%s:%d: missing proposal approval\n", filename, i+1) exitCode = 1 - } - _, err := strconv.Atoi(approval) - if err != nil { - log.Printf("%s:%d: malformed proposal approval #%s\n", filename, i+1, approval) - exitCode = 1 + } else { + _, err := strconv.Atoi(approval) + if err != nil { + log.Printf("%s:%d: malformed proposal approval #%s\n", filename, i+1, approval) + exitCode = 1 + } } line = strings.TrimSpace(feature) } else { @@ -383,9 +385,10 @@ type Walker struct { context *build.Context root string scope []string - current *types.Package + current *apiPackage + deprecated map[token.Pos]bool features map[string]bool // set - imported map[string]*types.Package // packages already imported + imported map[string]*apiPackage // packages already imported stdPackages []string // names, omitting "unsafe", internal, and vendored packages importMap map[string]map[string]string // importer dir -> import path -> canonical path importDir map[string]string // canonical import path -> dir @@ -397,7 +400,7 @@ func NewWalker(context *build.Context, root string) *Walker { context: context, root: root, features: map[string]bool{}, - imported: map[string]*types.Package{"unsafe": types.Unsafe}, + imported: map[string]*apiPackage{"unsafe": &apiPackage{Package: types.Unsafe}}, } w.loadImports() return w @@ -419,7 +422,7 @@ func (w *Walker) parseFile(dir, file string) (*ast.File, error) { return f, nil } - f, err := parser.ParseFile(fset, filename, nil, 0) + f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, err } @@ -432,8 +435,8 @@ func (w *Walker) parseFile(dir, file string) (*ast.File, error) { const usePkgCache = true var ( - pkgCache = map[string]*types.Package{} // map tagKey to package - pkgTags = map[string][]string{} // map import dir to list of relevant tags + pkgCache = map[string]*apiPackage{} // map tagKey to package + pkgTags = map[string][]string{} // map import dir to list of relevant tags ) // tagKey returns the tag-based key to use in the pkgCache. @@ -596,15 +599,34 @@ func listEnv(c *build.Context) []string { return environ } +type apiPackage struct { + *types.Package + Files []*ast.File +} + // Importing is a sentinel taking the place in Walker.imported // for a package that is in the process of being imported. -var importing types.Package +var importing apiPackage +// Import implements types.Importer. func (w *Walker) Import(name string) (*types.Package, error) { return w.ImportFrom(name, "", 0) } +// ImportFrom implements types.ImporterFrom. func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) { + pkg, err := w.importFrom(fromPath, fromDir, mode) + if err != nil { + return nil, err + } + return pkg.Package, nil +} + +func (w *Walker) import_(name string) (*apiPackage, error) { + return w.importFrom(name, "", 0) +} + +func (w *Walker) importFrom(fromPath, fromDir string, mode types.ImportMode) (*apiPackage, error) { name := fromPath if canonical, ok := w.importMap[fromDir][fromPath]; ok { name = canonical @@ -686,7 +708,7 @@ func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*t Importer: w, Sizes: sizes, } - pkg, err = conf.Check(name, fset, files, nil) + tpkg, err := conf.Check(name, fset, files, nil) if err != nil { ctxt := "<no context>" if w.context != nil { @@ -694,6 +716,7 @@ func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*t } log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt) } + pkg = &apiPackage{tpkg, files} if usePkgCache { pkgCache[key] = pkg @@ -854,7 +877,7 @@ func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { case *types.Named: obj := typ.Obj() pkg := obj.Pkg() - if pkg != nil && pkg != w.current { + if pkg != nil && pkg != w.current.Package { buf.WriteString(pkg.Name()) buf.WriteByte('.') } @@ -934,6 +957,9 @@ func (w *Walker) signatureString(sig *types.Signature) string { func (w *Walker) emitObj(obj types.Object) { switch obj := obj.(type) { case *types.Const: + if w.isDeprecated(obj) { + w.emitf("const %s //deprecated", obj.Name()) + } w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) x := obj.Val() short := x.String() @@ -944,6 +970,9 @@ func (w *Walker) emitObj(obj types.Object) { w.emitf("const %s = %s // %s", obj.Name(), short, exact) } case *types.Var: + if w.isDeprecated(obj) { + w.emitf("var %s //deprecated", obj.Name()) + } w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) case *types.TypeName: w.emitType(obj) @@ -956,6 +985,9 @@ func (w *Walker) emitObj(obj types.Object) { func (w *Walker) emitType(obj *types.TypeName) { name := obj.Name() + if w.isDeprecated(obj) { + w.emitf("type %s //deprecated", name) + } if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil { var buf bytes.Buffer buf.WriteString(name) @@ -1015,9 +1047,15 @@ func (w *Walker) emitStructType(name string, typ *types.Struct) { } typ := f.Type() if f.Anonymous() { + if w.isDeprecated(f) { + w.emitf("embedded %s //deprecated", w.typeString(typ)) + } w.emitf("embedded %s", w.typeString(typ)) continue } + if w.isDeprecated(f) { + w.emitf("%s //deprecated", f.Name()) + } w.emitf("%s %s", f.Name(), w.typeString(typ)) } } @@ -1035,6 +1073,9 @@ func (w *Walker) emitIfaceType(name string, typ *types.Interface) { continue } methodNames = append(methodNames, m.Name()) + if w.isDeprecated(m) { + w.emitf("%s //deprecated", m.Name()) + } w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature))) } @@ -1069,6 +1110,9 @@ func (w *Walker) emitFunc(f *types.Func) { if sig.Recv() != nil { panic("method considered a regular function: " + f.String()) } + if w.isDeprecated(f) { + w.emitf("func %s //deprecated", f.Name()) + } w.emitf("func %s%s", f.Name(), w.signatureString(sig)) } @@ -1091,6 +1135,9 @@ func (w *Walker) emitMethod(m *types.Selection) { w.writeTypeParams(&buf, rtp, false) tps = buf.String() } + if w.isDeprecated(m.Obj()) { + w.emitf("method (%s%s) %s //deprecated", w.typeString(recv), tps, m.Obj().Name()) + } w.emitf("method (%s%s) %s%s", w.typeString(recv), tps, m.Obj().Name(), w.signatureString(sig)) } @@ -1122,3 +1169,94 @@ func needApproval(filename string) bool { } return n >= 19 // started tracking approvals in Go 1.19 } + +func (w *Walker) collectDeprecated() { + isDeprecated := func(doc *ast.CommentGroup) bool { + if doc != nil { + for _, c := range doc.List { + if strings.HasPrefix(c.Text, "// Deprecated:") { + return true + } + } + } + return false + } + + w.deprecated = make(map[token.Pos]bool) + mark := func(id *ast.Ident) { + if id != nil { + w.deprecated[id.Pos()] = true + } + } + for _, file := range w.current.Files { + ast.Inspect(file, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.File: + if isDeprecated(n.Doc) { + mark(n.Name) + } + return true + case *ast.GenDecl: + if isDeprecated(n.Doc) { + for _, spec := range n.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + for _, id := range spec.Names { + mark(id) + } + case *ast.TypeSpec: + mark(spec.Name) + } + } + } + return true // look at specs + case *ast.FuncDecl: + if isDeprecated(n.Doc) { + mark(n.Name) + } + return false + case *ast.TypeSpec: + if isDeprecated(n.Doc) { + mark(n.Name) + } + return true // recurse into struct or interface type + case *ast.StructType: + return true // recurse into fields + case *ast.InterfaceType: + return true // recurse into methods + case *ast.FieldList: + return true // recurse into fields + case *ast.ValueSpec: + if isDeprecated(n.Doc) { + for _, id := range n.Names { + mark(id) + } + } + return false + case *ast.Field: + if isDeprecated(n.Doc) { + for _, id := range n.Names { + mark(id) + } + if len(n.Names) == 0 { + // embedded field T or *T? + typ := n.Type + if ptr, ok := typ.(*ast.StarExpr); ok { + typ = ptr.X + } + if id, ok := typ.(*ast.Ident); ok { + mark(id) + } + } + } + return false + default: + return false + } + }) + } +} + +func (w *Walker) isDeprecated(obj types.Object) bool { + return w.deprecated[obj.Pos()] +} diff --git a/src/cmd/api/api_test.go b/src/cmd/api/api_test.go index b215c48e73..5f9aa6d297 100644 --- a/src/cmd/api/api_test.go +++ b/src/cmd/api/api_test.go @@ -75,7 +75,7 @@ func TestGolden(t *testing.T) { // TODO(gri) remove extra pkg directory eventually goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt") w := NewWalker(nil, "testdata/src/pkg") - pkg, _ := w.Import(fi.Name()) + pkg, _ := w.import_(fi.Name()) w.export(pkg) if *updateGolden { @@ -202,7 +202,7 @@ func BenchmarkAll(b *testing.B) { for _, context := range contexts { w := NewWalker(context, filepath.Join(testenv.GOROOT(b), "src")) for _, name := range w.stdPackages { - pkg, _ := w.Import(name) + pkg, _ := w.import_(name) w.export(pkg) } w.Features() @@ -217,7 +217,7 @@ func TestIssue21181(t *testing.T) { } for _, context := range contexts { w := NewWalker(context, "testdata/src/issue21181") - pkg, err := w.Import("p") + pkg, err := w.import_("p") if err != nil { t.Fatalf("%s: (%s-%s) %s %v", err, context.GOOS, context.GOARCH, pkg.Name(), w.imported) @@ -233,7 +233,7 @@ func TestIssue29837(t *testing.T) { } for _, context := range contexts { w := NewWalker(context, "testdata/src/issue29837") - _, err := w.Import("p") + _, err := w.ImportFrom("p", "", 0) if _, nogo := err.(*build.NoGoError); !nogo { t.Errorf("expected *build.NoGoError, got %T", err) } diff --git a/src/cmd/api/testdata/src/pkg/p1/golden.txt b/src/cmd/api/testdata/src/pkg/p1/golden.txt index 0378a56870..65c4f35d2c 100644 --- a/src/cmd/api/testdata/src/pkg/p1/golden.txt +++ b/src/cmd/api/testdata/src/pkg/p1/golden.txt @@ -1,5 +1,6 @@ pkg p1, const A = 1 pkg p1, const A ideal-int +pkg p1, const A //deprecated pkg p1, const A64 = 1 pkg p1, const A64 int64 pkg p1, const AIsLowerA = 11 @@ -25,6 +26,7 @@ pkg p1, method (*B) JustOnB() pkg p1, method (*B) OnBothTandBPtr() pkg p1, method (*Embedded) OnEmbedded() pkg p1, method (*S2) SMethod(int8, int16, int64) +pkg p1, method (*S2) SMethod //deprecated pkg p1, method (*T) JustOnT() pkg p1, method (*T) OnBothTandBPtr() pkg p1, method (B) OnBothTandBVal() @@ -52,6 +54,7 @@ pkg p1, type Error interface, Error() string pkg p1, type Error interface, Temporary() bool pkg p1, type FuncType func(int, int, string) (*B, error) pkg p1, type I interface, Get(string) int64 +pkg p1, type I interface, Get //deprecated pkg p1, type I interface, GetNamed(string) int64 pkg p1, type I interface, Name() string pkg p1, type I interface, PackageTwoMeth() @@ -62,15 +65,18 @@ pkg p1, type Namer interface { Name } pkg p1, type Namer interface, Name() string pkg p1, type Private interface, X() pkg p1, type Private interface, unexported methods +pkg p1, type Private //deprecated pkg p1, type Public interface { X, Y } pkg p1, type Public interface, X() pkg p1, type Public interface, Y() pkg p1, type S struct pkg p1, type S struct, Public *int +pkg p1, type S struct, Public //deprecated pkg p1, type S struct, PublicTime Time pkg p1, type S2 struct pkg p1, type S2 struct, Extra bool pkg p1, type S2 struct, embedded S +pkg p1, type S2 struct, embedded S //deprecated pkg p1, type SI struct pkg p1, type SI struct, I int pkg p1, type T struct @@ -79,6 +85,7 @@ pkg p1, type TPtrExported struct, embedded *Embedded pkg p1, type TPtrUnexported struct pkg p1, type Time struct pkg p1, type URL struct +pkg p1, type URL //deprecated pkg p1, var Byte uint8 pkg p1, var ByteConv []uint8 pkg p1, var ByteFunc func(uint8) int32 @@ -91,6 +98,7 @@ pkg p1, var V string pkg p1, var V1 uint64 pkg p1, var V2 p2.Twoer pkg p1, var VError Error +pkg p1, var VError //deprecated pkg p1, var X I pkg p1, var X0 int64 pkg p1, var Y int diff --git a/src/cmd/api/testdata/src/pkg/p1/p1.go b/src/cmd/api/testdata/src/pkg/p1/p1.go index de00baf75f..025563dbf3 100644 --- a/src/cmd/api/testdata/src/pkg/p1/p1.go +++ b/src/cmd/api/testdata/src/pkg/p1/p1.go @@ -12,6 +12,7 @@ const ( ConstChase2 = constChase // forward declaration to unexported ident constChase = AIsLowerA // forward declaration to exported ident + // Deprecated: use B. A = 1 a = 11 A64 int64 = 1 @@ -25,7 +26,8 @@ const ( // Variables from function calls. var ( - V = ptwo.F() + V = ptwo.F() + // Deprecated: use WError. VError = BarE() V1 = Bar1(1, 2, 3) V2 = ptwo.G() @@ -50,11 +52,13 @@ type MyInt int type Time struct{} type S struct { + // Deprecated: use PublicTime. Public *int private *int PublicTime Time } +// Deprecated: use URI. type URL struct{} type EmbedURLPtr struct { @@ -62,6 +66,7 @@ type EmbedURLPtr struct { } type S2 struct { + // Deprecated: use T. S Extra bool } @@ -81,6 +86,7 @@ type I interface { Namer ptwo.Twoer Set(name string, balance int64) + // Deprecated: use GetNamed. Get(string) int64 GetNamed(string) (balance int64) private() @@ -91,6 +97,7 @@ type Public interface { Y() } +// Deprecated: Use Unexported. type Private interface { X() y() @@ -104,6 +111,7 @@ type Error interface { func (myInt) privateTypeMethod() {} func (myInt) CapitalMethodUnexportedType() {} +// Deprecated: use TMethod. func (s *S2) SMethod(x int8, y int16, z int64) {} type s struct{} diff --git a/src/cmd/api/testdata/src/pkg/p2/golden.txt b/src/cmd/api/testdata/src/pkg/p2/golden.txt index 4271620c74..735d668166 100644 --- a/src/cmd/api/testdata/src/pkg/p2/golden.txt +++ b/src/cmd/api/testdata/src/pkg/p2/golden.txt @@ -1,5 +1,8 @@ pkg p2, func F() string +pkg p2, func F //deprecated pkg p2, func G() Twoer pkg p2, func NewError(string) error pkg p2, type Twoer interface { PackageTwoMeth } pkg p2, type Twoer interface, PackageTwoMeth() +pkg p2, type Twoer interface, PackageTwoMeth //deprecated + diff --git a/src/cmd/api/testdata/src/pkg/p2/p2.go b/src/cmd/api/testdata/src/pkg/p2/p2.go index b179a399ee..2ce4e7587c 100644 --- a/src/cmd/api/testdata/src/pkg/p2/p2.go +++ b/src/cmd/api/testdata/src/pkg/p2/p2.go @@ -5,9 +5,13 @@ package p2 type Twoer interface { + // Deprecated: No good. PackageTwoMeth() } -func F() string {} -func G() Twoer {} +// Deprecated: No good. +func F() string {} + +func G() Twoer {} + func NewError(s string) error {} diff --git a/src/cmd/api/testdata/src/pkg/p4/golden.txt b/src/cmd/api/testdata/src/pkg/p4/golden.txt index 7997ab4471..eec0598dcd 100644 --- a/src/cmd/api/testdata/src/pkg/p4/golden.txt +++ b/src/cmd/api/testdata/src/pkg/p4/golden.txt @@ -3,3 +3,4 @@ pkg p4, method (Pair[$0, $1]) Second() $1 pkg p4, method (Pair[$0, $1]) First() $0 pkg p4, type Pair[$0 interface{ M }, $1 interface{ ~int }] struct pkg p4, func Clone[$0 interface{ ~[]$1 }, $1 interface{}]($0) $0 +pkg p4, func Clone //deprecated diff --git a/src/cmd/api/testdata/src/pkg/p4/p4.go b/src/cmd/api/testdata/src/pkg/p4/p4.go index 1f90e779dd..6c93e3e6f1 100644 --- a/src/cmd/api/testdata/src/pkg/p4/p4.go +++ b/src/cmd/api/testdata/src/pkg/p4/p4.go @@ -21,6 +21,7 @@ func (p Pair[_, X2]) Second() X2 { return p.f2 } +// Deprecated: Use something else. func Clone[S ~[]T, T any](s S) S { return append(S(nil), s...) } |
