internal/godoc: make work with generics
We need to register a new AST node to support
encoding.
Also add a test.
For golang/go#48264
Change-Id: Ib9a0205ff4daeb55ee3447945fca513d2e4cb9c8
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/382977
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/godoc/encode_ast.gen.go b/internal/godoc/encode_ast.gen.go
index a236aa3..27d3906 100644
--- a/internal/godoc/encode_ast.gen.go
+++ b/internal/godoc/encode_ast.gen.go
@@ -1604,7 +1604,7 @@
})
}
-// Fields of ast_FuncType: Func Params Results
+// Fields of ast_FuncType: Func Params Results TypeParams
func encode_ast_FuncType(e *codec.Encoder, x *ast.FuncType) {
if !e.StartStruct(x == nil, x) {
@@ -1622,6 +1622,10 @@
e.EncodeUint(2)
encode_ast_FieldList(e, x.Results)
}
+ if x.TypeParams != nil {
+ e.EncodeUint(3)
+ encode_ast_FieldList(e, x.TypeParams)
+ }
e.EndStruct()
}
@@ -1648,6 +1652,8 @@
decode_ast_FieldList(d, &x.Params)
case 2:
decode_ast_FieldList(d, &x.Results)
+ case 3:
+ decode_ast_FieldList(d, &x.TypeParams)
default:
d.UnknownField("ast.FuncType", n)
}
@@ -2134,6 +2140,73 @@
})
}
+// Fields of ast_IndexListExpr: X Lbrack Indices Rbrack
+
+func encode_ast_IndexListExpr(e *codec.Encoder, x *ast.IndexListExpr) {
+ if !e.StartStruct(x == nil, x) {
+ return
+ }
+ if x.X != nil {
+ e.EncodeUint(0)
+ e.EncodeAny(x.X)
+ }
+ if x.Lbrack != 0 {
+ e.EncodeUint(1)
+ e.EncodeInt(int64(x.Lbrack))
+ }
+ if x.Indices != nil {
+ e.EncodeUint(2)
+ encode_slice_ast_Expr(e, x.Indices)
+ }
+ if x.Rbrack != 0 {
+ e.EncodeUint(3)
+ e.EncodeInt(int64(x.Rbrack))
+ }
+ e.EndStruct()
+}
+
+func decode_ast_IndexListExpr(d *codec.Decoder, p **ast.IndexListExpr) {
+ proceed, ref := d.StartStruct()
+ if !proceed {
+ return
+ }
+ if ref != nil {
+ *p = ref.(*ast.IndexListExpr)
+ return
+ }
+ var x ast.IndexListExpr
+ d.StoreRef(&x)
+ for {
+ n := d.NextStructField()
+ if n < 0 {
+ break
+ }
+ switch n {
+ case 0:
+ x.X = d.DecodeAny().(ast.Expr)
+ case 1:
+ x.Lbrack = token.Pos(d.DecodeInt())
+ case 2:
+ decode_slice_ast_Expr(d, &x.Indices)
+ case 3:
+ x.Rbrack = token.Pos(d.DecodeInt())
+ default:
+ d.UnknownField("ast.IndexListExpr", n)
+ }
+ *p = &x
+ }
+}
+
+func init() {
+ codec.Register(&ast.IndexListExpr{},
+ func(e *codec.Encoder, x interface{}) { encode_ast_IndexListExpr(e, x.(*ast.IndexListExpr)) },
+ func(d *codec.Decoder) interface{} {
+ var x *ast.IndexListExpr
+ decode_ast_IndexListExpr(d, &x)
+ return x
+ })
+}
+
// Fields of ast_InterfaceType: Interface Methods Incomplete
func encode_ast_InterfaceType(e *codec.Encoder, x *ast.InterfaceType) {
@@ -3140,7 +3213,7 @@
})
}
-// Fields of ast_TypeSpec: Doc Name Assign Type Comment
+// Fields of ast_TypeSpec: Doc Name Assign Type Comment TypeParams
func encode_ast_TypeSpec(e *codec.Encoder, x *ast.TypeSpec) {
if !e.StartStruct(x == nil, x) {
@@ -3166,6 +3239,10 @@
e.EncodeUint(4)
encode_ast_CommentGroup(e, x.Comment)
}
+ if x.TypeParams != nil {
+ e.EncodeUint(5)
+ encode_ast_FieldList(e, x.TypeParams)
+ }
e.EndStruct()
}
@@ -3196,6 +3273,8 @@
x.Type = d.DecodeAny().(ast.Expr)
case 4:
decode_ast_CommentGroup(d, &x.Comment)
+ case 5:
+ decode_ast_FieldList(d, &x.TypeParams)
default:
d.UnknownField("ast.TypeSpec", n)
}
diff --git a/internal/godoc/gen_ast.go b/internal/godoc/gen_ast.go
index dad03b5..b01d049 100644
--- a/internal/godoc/gen_ast.go
+++ b/internal/godoc/gen_ast.go
@@ -52,6 +52,7 @@
ast.ImportSpec{},
ast.IncDecStmt{},
ast.IndexExpr{},
+ ast.IndexListExpr{},
ast.InterfaceType{},
ast.KeyValueExpr{},
ast.LabeledStmt{},
diff --git a/internal/godoc/internal/doc/reader.go b/internal/godoc/internal/doc/reader.go
index 20b1e3c..a1f9141 100644
--- a/internal/godoc/internal/doc/reader.go
+++ b/internal/godoc/internal/doc/reader.go
@@ -5,10 +5,12 @@
package doc
import (
+ "fmt"
"go/ast"
"go/token"
"sort"
"strconv"
+ "strings"
"golang.org/x/pkgsite/internal/godoc/internal/lazyregexp"
)
@@ -23,8 +25,8 @@
//
type methodSet map[string]*Func
-// recvString returns a string representation of recv of the
-// form "T", "*T", or "BADRECV" (if not a proper receiver type).
+// recvString returns a string representation of recv of the form "T", "*T",
+// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).
//
func recvString(recv ast.Expr) string {
switch t := recv.(type) {
@@ -32,10 +34,34 @@
return t.Name
case *ast.StarExpr:
return "*" + recvString(t.X)
+ case *ast.IndexExpr:
+ // Generic type with one parameter.
+ return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))
+ case *ast.IndexListExpr:
+ // Generic type with multiple parameters.
+ if len(t.Indices) > 0 {
+ var b strings.Builder
+ b.WriteString(recvString(t.X))
+ b.WriteByte('[')
+ b.WriteString(recvParam(t.Indices[0]))
+ for _, e := range t.Indices[1:] {
+ b.WriteString(", ")
+ b.WriteString(recvParam(e))
+ }
+ b.WriteByte(']')
+ return b.String()
+ }
}
return "BADRECV"
}
+func recvParam(p ast.Expr) string {
+ if id, ok := p.(*ast.Ident); ok {
+ return id.Name
+ }
+ return "BADPARAM"
+}
+
// set creates the corresponding Func for f and adds it to mset.
// If there are multiple f's with the same name, set keeps the first
// one with documentation; conflicts are ignored. The boolean
diff --git a/internal/godoc/render_test.go b/internal/godoc/render_test.go
index c8ea7c9..75deb33 100644
--- a/internal/godoc/render_test.go
+++ b/internal/godoc/render_test.go
@@ -36,53 +36,58 @@
ModulePackages: nil,
}
- // Use a Package created locally and without nodes removed as the desired doc.
- p, err := packageForDir(filepath.Join("testdata", "p"), false)
- if err != nil {
- t.Fatal(err)
+ for _, name := range []string{"p", "generics"} {
+ t.Run(name, func(t *testing.T) {
+ // Use a Package created locally and without nodes removed as the desired doc.
+ p, err := packageForDir(filepath.Join("testdata", name), false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ wantSyn, wantImports, _, err := p.DocInfo(ctx, name, si, mi)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ check := func(p *Package) {
+ t.Helper()
+ gotSyn, gotImports, _, err := p.DocInfo(ctx, name, si, mi)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if gotSyn != wantSyn {
+ t.Errorf("synopsis: got %q, want %q", gotSyn, wantSyn)
+ }
+ if !cmp.Equal(gotImports, wantImports) {
+ t.Errorf("imports: got %v, want %v", gotImports, wantImports)
+ }
+ }
+
+ // Verify that removing AST nodes doesn't change the doc.
+ p, err = packageForDir(filepath.Join("testdata", name), true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ check(p)
+
+ // Verify that encoding then decoding generates the same doc.
+ // We can't re-use p to encode because it's been rendered.
+ p, err = packageForDir(filepath.Join("testdata", name), true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bytes, err := p.Encode(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ p2, err := DecodePackage(bytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+ check(p2)
+ })
}
- wantSyn, wantImports, _, err := p.DocInfo(ctx, "p", si, mi)
- if err != nil {
- t.Fatal(err)
- }
-
- check := func(p *Package) {
- t.Helper()
- gotSyn, gotImports, _, err := p.DocInfo(ctx, "p", si, mi)
- if err != nil {
- t.Fatal(err)
- }
- if gotSyn != wantSyn {
- t.Errorf("synopsis: got %q, want %q", gotSyn, wantSyn)
- }
- if !cmp.Equal(gotImports, wantImports) {
- t.Errorf("imports: got %v, want %v", gotImports, wantImports)
- }
- }
-
- // Verify that removing AST nodes doesn't change the doc.
- p, err = packageForDir(filepath.Join("testdata", "p"), true)
- if err != nil {
- t.Fatal(err)
- }
- check(p)
-
- // Verify that encoding then decoding generates the same doc.
- // We can't re-use p to encode because it's been rendered.
- p, err = packageForDir(filepath.Join("testdata", "p"), true)
- if err != nil {
- t.Fatal(err)
- }
- bytes, err := p.Encode(ctx)
- if err != nil {
- t.Fatal(err)
- }
- p2, err := DecodePackage(bytes)
- if err != nil {
- t.Fatal(err)
- }
- check(p2)
}
func TestRenderParts_SinceVersion(t *testing.T) {
diff --git a/internal/godoc/testdata/generics/g.go b/internal/godoc/testdata/generics/g.go
new file mode 100644
index 0000000..fa7cc53
--- /dev/null
+++ b/internal/godoc/testdata/generics/g.go
@@ -0,0 +1,16 @@
+// 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 uses generics.
+package generics
+
+type Pair[A, B any] struct {
+ V0 A
+ V1 B
+}
+
+// NewPair returns a new Pair.
+func NewPair[A, B any](a A, b B) Pair[A, B] {
+ return Pair[A, B]{a, b}
+}