1
0
mirror of https://github.com/golang/go synced 2024-11-24 12:40:12 -07:00

cmd/doc, go/doc: add basic support for generic code

Update cmd/doc and go/doc for the generics, by adding handling for type
parameters and the new embedded interface elements.

Specifically:
 - Format type parameters when summarizing type and function nodes.
 - Find the origin type name for instantiation expressions, so that
   methods are associated with generic type declarations.
 - Generalize the handling of embedding 'error' in interfaces to
   arbitrary predeclared types.
 - Keep embedded type literals.
 - Update filtering to descend into embedded type literals.

Also add "any" to the list of predeclared types.

Updates #49210

Change-Id: I6ea82869f19c3cdbc3c842f01581c8fc7e1c2ee7
Reviewed-on: https://go-review.googlesource.com/c/go/+/359778
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Robert Findley 2021-10-29 17:41:41 -04:00
parent 901bf291bc
commit 00d6d2037e
8 changed files with 339 additions and 32 deletions

View File

@ -323,7 +323,8 @@ func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {
if n.Assign.IsValid() { if n.Assign.IsValid() {
sep = " = " sep = " = "
} }
return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, pkg.oneLineNodeDepth(n.Type, depth)) tparams := pkg.formatTypeParams(n.TypeParams, depth)
return fmt.Sprintf("type %s%s%s%s", n.Name.Name, tparams, sep, pkg.oneLineNodeDepth(n.Type, depth))
case *ast.FuncType: case *ast.FuncType:
var params []string var params []string
@ -342,15 +343,16 @@ func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {
} }
} }
tparam := pkg.formatTypeParams(n.TypeParams, depth)
param := joinStrings(params) param := joinStrings(params)
if len(results) == 0 { if len(results) == 0 {
return fmt.Sprintf("func(%s)", param) return fmt.Sprintf("func%s(%s)", tparam, param)
} }
result := joinStrings(results) result := joinStrings(results)
if !needParens { if !needParens {
return fmt.Sprintf("func(%s) %s", param, result) return fmt.Sprintf("func%s(%s) %s", tparam, param, result)
} }
return fmt.Sprintf("func(%s) (%s)", param, result) return fmt.Sprintf("func%s(%s) (%s)", tparam, param, result)
case *ast.StructType: case *ast.StructType:
if n.Fields == nil || len(n.Fields.List) == 0 { if n.Fields == nil || len(n.Fields.List) == 0 {
@ -419,6 +421,17 @@ func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {
} }
} }
func (pkg *Package) formatTypeParams(list *ast.FieldList, depth int) string {
if list.NumFields() == 0 {
return ""
}
var tparams []string
for _, field := range list.List {
tparams = append(tparams, pkg.oneLineField(field, depth))
}
return "[" + joinStrings(tparams) + "]"
}
// oneLineField returns a one-line summary of the field. // oneLineField returns a one-line summary of the field.
func (pkg *Package) oneLineField(field *ast.Field, depth int) string { func (pkg *Package) oneLineField(field *ast.Field, depth int) string {
var names []string var names []string

View File

@ -79,18 +79,15 @@ func hasExportedName(list []*ast.Ident) bool {
return false return false
} }
// removeErrorField removes anonymous fields named "error" from an interface. // removeAnonymousField removes anonymous fields named name from an interface.
// This is called when "error" has been determined to be a local name, func removeAnonymousField(name string, ityp *ast.InterfaceType) {
// not the predeclared type.
//
func removeErrorField(ityp *ast.InterfaceType) {
list := ityp.Methods.List // we know that ityp.Methods != nil list := ityp.Methods.List // we know that ityp.Methods != nil
j := 0 j := 0
for _, field := range list { for _, field := range list {
keepField := true keepField := true
if n := len(field.Names); n == 0 { if n := len(field.Names); n == 0 {
// anonymous field // anonymous field
if fname, _ := baseTypeName(field.Type); fname == "error" { if fname, _ := baseTypeName(field.Type); fname == name {
keepField = false keepField = false
} }
} }
@ -119,16 +116,25 @@ func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp
for _, field := range list { for _, field := range list {
keepField := false keepField := false
if n := len(field.Names); n == 0 { if n := len(field.Names); n == 0 {
// anonymous field // anonymous field or embedded type or union element
fname := r.recordAnonymousField(parent, field.Type) fname := r.recordAnonymousField(parent, field.Type)
if token.IsExported(fname) { if fname != "" {
keepField = true if token.IsExported(fname) {
} else if ityp != nil && fname == "error" { keepField = true
// possibly the predeclared error interface; keep } else if ityp != nil && predeclaredTypes[fname] {
// it for now but remember this interface so that // possibly an embedded predeclared type; keep it for now but
// it can be fixed if error is also defined locally // remember this interface so that it can be fixed if name is also
keepField = true // defined locally
r.remember(ityp) keepField = true
r.remember(fname, ityp)
}
} else {
// If we're operating on an interface, assume that this is an embedded
// type or union element.
//
// TODO(rfindley): consider traversing into approximation/unions
// elements to see if they are entirely unexported.
keepField = ityp != nil
} }
} else { } else {
field.Names = filterIdentList(field.Names) field.Names = filterIdentList(field.Names)
@ -172,6 +178,17 @@ func (r *reader) filterType(parent *namedType, typ ast.Expr) {
// nothing to do // nothing to do
case *ast.ParenExpr: case *ast.ParenExpr:
r.filterType(nil, t.X) r.filterType(nil, t.X)
case *ast.StarExpr: // possibly an embedded type literal
r.filterType(nil, t.X)
case *ast.UnaryExpr:
if t.Op == token.TILDE { // approximation element
r.filterType(nil, t.X)
}
case *ast.BinaryExpr:
if t.Op == token.OR { // union
r.filterType(nil, t.X)
r.filterType(nil, t.Y)
}
case *ast.ArrayType: case *ast.ArrayType:
r.filterType(nil, t.Elt) r.filterType(nil, t.Elt)
case *ast.StructType: case *ast.StructType:
@ -179,6 +196,7 @@ func (r *reader) filterType(parent *namedType, typ ast.Expr) {
t.Incomplete = true t.Incomplete = true
} }
case *ast.FuncType: case *ast.FuncType:
r.filterParamList(t.TypeParams)
r.filterParamList(t.Params) r.filterParamList(t.Params)
r.filterParamList(t.Results) r.filterParamList(t.Results)
case *ast.InterfaceType: case *ast.InterfaceType:
@ -219,12 +237,16 @@ func (r *reader) filterSpec(spec ast.Spec) bool {
} }
} }
case *ast.TypeSpec: case *ast.TypeSpec:
// Don't filter type parameters here, by analogy with function parameters
// which are not filtered for top-level function declarations.
if name := s.Name.Name; token.IsExported(name) { if name := s.Name.Name; token.IsExported(name) {
r.filterType(r.lookupType(s.Name.Name), s.Type) r.filterType(r.lookupType(s.Name.Name), s.Type)
return true return true
} else if name == "error" { } else if IsPredeclared(name) {
// special case: remember that error is declared locally if r.shadowedPredecl == nil {
r.errorDecl = true r.shadowedPredecl = make(map[string]bool)
}
r.shadowedPredecl[name] = true
} }
} }
return false return false

View File

@ -34,6 +34,8 @@ func matchDecl(d *ast.GenDecl, f Filter) bool {
if f(v.Name.Name) { if f(v.Name.Name) {
return true return true
} }
// We don't match ordinary parameters in filterFuncs, so by analogy don't
// match type parameters here.
switch t := v.Type.(type) { switch t := v.Type.(type) {
case *ast.StructType: case *ast.StructType:
if matchFields(t.Fields, f) { if matchFields(t.Fields, f) {

View File

@ -101,6 +101,10 @@ func baseTypeName(x ast.Expr) (name string, imported bool) {
switch t := x.(type) { switch t := x.(type) {
case *ast.Ident: case *ast.Ident:
return t.Name, false return t.Name, false
case *ast.IndexExpr:
return baseTypeName(t.X)
case *ast.IndexListExpr:
return baseTypeName(t.X)
case *ast.SelectorExpr: case *ast.SelectorExpr:
if _, ok := t.X.(*ast.Ident); ok { if _, ok := t.X.(*ast.Ident); ok {
// only possible for qualified type names; // only possible for qualified type names;
@ -112,7 +116,7 @@ func baseTypeName(x ast.Expr) (name string, imported bool) {
case *ast.StarExpr: case *ast.StarExpr:
return baseTypeName(t.X) return baseTypeName(t.X)
} }
return return "", false
} }
// An embeddedSet describes a set of embedded types. // An embeddedSet describes a set of embedded types.
@ -163,9 +167,9 @@ type reader struct {
types map[string]*namedType types map[string]*namedType
funcs methodSet funcs methodSet
// support for package-local error type declarations // support for package-local shadowing of predeclared types
errorDecl bool // if set, type "error" was declared locally shadowedPredecl map[string]bool
fixlist []*ast.InterfaceType // list of interfaces containing anonymous field "error" fixmap map[string][]*ast.InterfaceType
} }
func (r *reader) isVisible(name string) bool { func (r *reader) isVisible(name string) bool {
@ -224,8 +228,11 @@ func (r *reader) readDoc(comment *ast.CommentGroup) {
r.doc += "\n" + text r.doc += "\n" + text
} }
func (r *reader) remember(typ *ast.InterfaceType) { func (r *reader) remember(predecl string, typ *ast.InterfaceType) {
r.fixlist = append(r.fixlist, typ) if r.fixmap == nil {
r.fixmap = make(map[string][]*ast.InterfaceType)
}
r.fixmap[predecl] = append(r.fixmap[predecl], typ)
} }
func specNames(specs []ast.Spec) []string { func specNames(specs []ast.Spec) []string {
@ -679,10 +686,11 @@ func (r *reader) computeMethodSets() {
} }
} }
// if error was declared locally, don't treat it as exported field anymore // For any predeclared names that are declared locally, don't treat them as
if r.errorDecl { // exported fields anymore.
for _, ityp := range r.fixlist { for predecl := range r.shadowedPredecl {
removeErrorField(ityp) for _, ityp := range r.fixmap[predecl] {
removeAnonymousField(predecl, ityp)
} }
} }
} }
@ -869,6 +877,7 @@ func IsPredeclared(s string) bool {
} }
var predeclaredTypes = map[string]bool{ var predeclaredTypes = map[string]bool{
"any": true,
"bool": true, "bool": true,
"byte": true, "byte": true,
"complex64": true, "complex64": true,

70
src/go/doc/testdata/generics.0.golden vendored Normal file
View File

@ -0,0 +1,70 @@
// Package generics contains the new syntax supporting generic ...
PACKAGE generics
IMPORTPATH
testdata/generics
FILENAMES
testdata/generics.go
FUNCTIONS
// AnotherFunc has an implicit constraint interface. Neither type ...
func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
// Func has an instantiated constraint.
func Func[T Constraint[string, Type[int]]]()
TYPES
// AFuncType demonstrates filtering of parameters and type ...
type AFuncType[T ~struct{ f int }] func(_ struct {
// contains filtered or unexported fields
})
// Constraint is a constraint interface with two type parameters.
type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
~int | ~byte | Type[string]
M() P
}
// NewEmbeddings demonstrates how we filter the new embedded ...
type NewEmbeddings interface {
string // should not be filtered
struct {
// contains filtered or unexported fields
}
~struct {
// contains filtered or unexported fields
}
*struct {
// contains filtered or unexported fields
}
struct {
// contains filtered or unexported fields
} | ~struct {
// contains filtered or unexported fields
}
// contains filtered or unexported methods
}
// Parameterized types should be shown.
type Type[P any] struct {
Field P
}
// Variables with an instantiated type should be shown.
var X Type[int]
// Constructors for parameterized types should be shown.
func Constructor[lowerCase any]() Type[lowerCase]
// MethodA uses a different name for its receiver type parameter.
func (t Type[A]) MethodA(p A)
// MethodB has a blank receiver type parameter.
func (t Type[_]) MethodB()
// MethodC has a lower-case receiver type parameter.
func (t Type[c]) MethodC()

60
src/go/doc/testdata/generics.1.golden vendored Normal file
View File

@ -0,0 +1,60 @@
// Package generics contains the new syntax supporting generic ...
PACKAGE generics
IMPORTPATH
testdata/generics
FILENAMES
testdata/generics.go
FUNCTIONS
// AnotherFunc has an implicit constraint interface. Neither type ...
func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
// Func has an instantiated constraint.
func Func[T Constraint[string, Type[int]]]()
TYPES
// AFuncType demonstrates filtering of parameters and type ...
type AFuncType[T ~struct{ f int }] func(_ struct{ f int })
// Constraint is a constraint interface with two type parameters.
type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
~int | ~byte | Type[string]
M() P
}
// NewEmbeddings demonstrates how we filter the new embedded ...
type NewEmbeddings interface {
string // should not be filtered
int16
struct{ f int }
~struct{ f int }
*struct{ f int }
struct{ f int } | ~struct{ f int }
}
// Parameterized types should be shown.
type Type[P any] struct {
Field P
}
// Variables with an instantiated type should be shown.
var X Type[int]
// Constructors for parameterized types should be shown.
func Constructor[lowerCase any]() Type[lowerCase]
// MethodA uses a different name for its receiver type parameter.
func (t Type[A]) MethodA(p A)
// MethodB has a blank receiver type parameter.
func (t Type[_]) MethodB()
// MethodC has a lower-case receiver type parameter.
func (t Type[c]) MethodC()
// int16 shadows the predeclared type int16.
type int16 int

70
src/go/doc/testdata/generics.2.golden vendored Normal file
View File

@ -0,0 +1,70 @@
// Package generics contains the new syntax supporting generic ...
PACKAGE generics
IMPORTPATH
testdata/generics
FILENAMES
testdata/generics.go
FUNCTIONS
// AnotherFunc has an implicit constraint interface. Neither type ...
func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
// Func has an instantiated constraint.
func Func[T Constraint[string, Type[int]]]()
TYPES
// AFuncType demonstrates filtering of parameters and type ...
type AFuncType[T ~struct{ f int }] func(_ struct {
// contains filtered or unexported fields
})
// Constraint is a constraint interface with two type parameters.
type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
~int | ~byte | Type[string]
M() P
}
// NewEmbeddings demonstrates how we filter the new embedded ...
type NewEmbeddings interface {
string // should not be filtered
struct {
// contains filtered or unexported fields
}
~struct {
// contains filtered or unexported fields
}
*struct {
// contains filtered or unexported fields
}
struct {
// contains filtered or unexported fields
} | ~struct {
// contains filtered or unexported fields
}
// contains filtered or unexported methods
}
// Parameterized types should be shown.
type Type[P any] struct {
Field P
}
// Variables with an instantiated type should be shown.
var X Type[int]
// Constructors for parameterized types should be shown.
func Constructor[lowerCase any]() Type[lowerCase]
// MethodA uses a different name for its receiver type parameter.
func (t Type[A]) MethodA(p A)
// MethodB has a blank receiver type parameter.
func (t Type[_]) MethodB()
// MethodC has a lower-case receiver type parameter.
func (t Type[c]) MethodC()

61
src/go/doc/testdata/generics.go vendored Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package generics contains the new syntax supporting generic programming in
// Go.
package generics
// Variables with an instantiated type should be shown.
var X Type[int]
// Parameterized types should be shown.
type Type[P any] struct {
Field P
}
// Constructors for parameterized types should be shown.
func Constructor[lowerCase any]() Type[lowerCase] {
return Type[lowerCase]{}
}
// MethodA uses a different name for its receiver type parameter.
func (t Type[A]) MethodA(p A) {}
// MethodB has a blank receiver type parameter.
func (t Type[_]) MethodB() {}
// MethodC has a lower-case receiver type parameter.
func (t Type[c]) MethodC() {}
// Constraint is a constraint interface with two type parameters.
type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
~int | ~byte | Type[string]
M() P
}
// int16 shadows the predeclared type int16.
type int16 int
// NewEmbeddings demonstrates how we filter the new embedded elements.
type NewEmbeddings interface {
string // should not be filtered
int16
struct{ f int }
~struct{ f int }
*struct{ f int }
struct{ f int } | ~struct{ f int }
}
// Func has an instantiated constraint.
func Func[T Constraint[string, Type[int]]]() {}
// AnotherFunc has an implicit constraint interface.
//
// Neither type parameters nor regular parameters should be filtered.
func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) {}
// AFuncType demonstrates filtering of parameters and type parameters. Here we
// don't filter type parameters (to be consistent with function declarations),
// but DO filter the RHS.
type AFuncType[T ~struct{ f int }] func(_ struct{ f int })