aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/api
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2022-11-23 13:45:56 -0500
committerGopher Robot <gobot@golang.org>2022-12-02 16:29:41 +0000
commit1711f953e48c3de0f85726cb0299c0c0f0b928a1 (patch)
tree270ac0747740ad7c5e016f417d36ed3e03a3e585 /src/cmd/api
parentdadd80ae204bda1c3a48245d8a938f55f71259ea (diff)
downloadgo-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.go170
-rw-r--r--src/cmd/api/api_test.go8
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/golden.txt8
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/p1.go10
-rw-r--r--src/cmd/api/testdata/src/pkg/p2/golden.txt3
-rw-r--r--src/cmd/api/testdata/src/pkg/p2/p2.go8
-rw-r--r--src/cmd/api/testdata/src/pkg/p4/golden.txt1
-rw-r--r--src/cmd/api/testdata/src/pkg/p4/p4.go1
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...)
}