mirror of
https://github.com/golang/go
synced 2024-11-23 22:30:05 -07:00
go/doc: handle generic receiver strings
A receiver expression for a type with parameters may be an IndexExpr or IndexListExpr in addition to an Ident or StarExpr. Add cases to recvString to account for the new types. Add tests that compare the fields of Func, and the fields of Type that hold Funcs. These fields weren't previously tested. Change-Id: Ia2cef51c85113422e0c5745c77dddcd53507ee51 Reviewed-on: https://go-review.googlesource.com/c/go/+/375095 Trust: Jonathan Amsterdam <jba@google.com> Run-TryBot: Jonathan Amsterdam <jba@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
a893d0f464
commit
95b240b2cd
@ -160,3 +160,142 @@ func TestAnchorID(t *testing.T) {
|
|||||||
t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
|
t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFuncs(t *testing.T) {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range doc.Funcs {
|
||||||
|
f.Decl = nil
|
||||||
|
}
|
||||||
|
for _, ty := range doc.Types {
|
||||||
|
for _, f := range ty.Funcs {
|
||||||
|
f.Decl = nil
|
||||||
|
}
|
||||||
|
for _, m := range ty.Methods {
|
||||||
|
m.Decl = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareFuncs := func(t *testing.T, msg string, got, want *Func) {
|
||||||
|
// ignore Decl and Examples
|
||||||
|
got.Decl = nil
|
||||||
|
got.Examples = nil
|
||||||
|
if !(got.Doc == want.Doc &&
|
||||||
|
got.Name == want.Name &&
|
||||||
|
got.Recv == want.Recv &&
|
||||||
|
got.Orig == want.Orig &&
|
||||||
|
got.Level == want.Level) {
|
||||||
|
t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
|
||||||
|
compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
|
||||||
|
if got.Name != want.Name {
|
||||||
|
t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
|
||||||
|
} else {
|
||||||
|
compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
|
||||||
|
compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Errorf("%s: got %d, want %d", name, len(got), len(want))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(got) && i < len(want); i++ {
|
||||||
|
compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcsTestFile = `
|
||||||
|
package funcs
|
||||||
|
|
||||||
|
func F() {}
|
||||||
|
|
||||||
|
type S1 struct {
|
||||||
|
S2 // embedded, exported
|
||||||
|
s3 // embedded, unexported
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS1() S1 {return S1{} }
|
||||||
|
func NewS1p() *S1 { return &S1{} }
|
||||||
|
|
||||||
|
func (S1) M1() {}
|
||||||
|
func (r S1) M2() {}
|
||||||
|
func(S1) m3() {} // unexported not shown
|
||||||
|
func (*S1) P1() {} // pointer receiver
|
||||||
|
|
||||||
|
type S2 int
|
||||||
|
func (S2) M3() {} // shown on S2
|
||||||
|
|
||||||
|
type s3 int
|
||||||
|
func (s3) M4() {} // shown on S1
|
||||||
|
|
||||||
|
type G1[T any] struct {
|
||||||
|
*s3
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewG1[T any]() G1[T] { return G1[T]{} }
|
||||||
|
|
||||||
|
func (G1[T]) MG1() {}
|
||||||
|
func (*G1[U]) MG2() {}
|
||||||
|
|
||||||
|
type G2[T, U any] struct {}
|
||||||
|
|
||||||
|
func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
|
||||||
|
|
||||||
|
func (G2[T, U]) MG3() {}
|
||||||
|
func (*G2[A, B]) MG4() {}
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var funcsPackage = &Package{
|
||||||
|
Funcs: []*Func{{Name: "F"}},
|
||||||
|
Types: []*Type{
|
||||||
|
{
|
||||||
|
Name: "G1",
|
||||||
|
Funcs: []*Func{{Name: "NewG1"}},
|
||||||
|
Methods: []*Func{
|
||||||
|
{Name: "M4", Recv: "G1", // TODO: synthesize a param for G1?
|
||||||
|
Orig: "s3", Level: 1},
|
||||||
|
{Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
|
||||||
|
{Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "G2",
|
||||||
|
Funcs: []*Func{{Name: "NewG2"}},
|
||||||
|
Methods: []*Func{
|
||||||
|
{Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
|
||||||
|
{Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "S1",
|
||||||
|
Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
|
||||||
|
Methods: []*Func{
|
||||||
|
{Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
|
||||||
|
{Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
|
||||||
|
{Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
|
||||||
|
{Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "S2",
|
||||||
|
Methods: []*Func{
|
||||||
|
{Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
package doc
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"internal/lazyregexp"
|
"internal/lazyregexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -22,8 +24,8 @@ import (
|
|||||||
//
|
//
|
||||||
type methodSet map[string]*Func
|
type methodSet map[string]*Func
|
||||||
|
|
||||||
// recvString returns a string representation of recv of the
|
// recvString returns a string representation of recv of the form "T", "*T",
|
||||||
// form "T", "*T", or "BADRECV" (if not a proper receiver type).
|
// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).
|
||||||
//
|
//
|
||||||
func recvString(recv ast.Expr) string {
|
func recvString(recv ast.Expr) string {
|
||||||
switch t := recv.(type) {
|
switch t := recv.(type) {
|
||||||
@ -31,10 +33,34 @@ func recvString(recv ast.Expr) string {
|
|||||||
return t.Name
|
return t.Name
|
||||||
case *ast.StarExpr:
|
case *ast.StarExpr:
|
||||||
return "*" + recvString(t.X)
|
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"
|
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.
|
// 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
|
// If there are multiple f's with the same name, set keeps the first
|
||||||
// one with documentation; conflicts are ignored. The boolean
|
// one with documentation; conflicts are ignored. The boolean
|
||||||
|
Loading…
Reference in New Issue
Block a user