Skip to content

Commit 95b240b

Browse files
committed
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 <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent a893d0f commit 95b240b

File tree

2 files changed

+167
-2
lines changed

2 files changed

+167
-2
lines changed

src/go/doc/doc_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,142 @@ func TestAnchorID(t *testing.T) {
160160
t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
161161
}
162162
}
163+
164+
func TestFuncs(t *testing.T) {
165+
fset := token.NewFileSet()
166+
file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
167+
if err != nil {
168+
t.Fatal(err)
169+
}
170+
doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
171+
if err != nil {
172+
t.Fatal(err)
173+
}
174+
175+
for _, f := range doc.Funcs {
176+
f.Decl = nil
177+
}
178+
for _, ty := range doc.Types {
179+
for _, f := range ty.Funcs {
180+
f.Decl = nil
181+
}
182+
for _, m := range ty.Methods {
183+
m.Decl = nil
184+
}
185+
}
186+
187+
compareFuncs := func(t *testing.T, msg string, got, want *Func) {
188+
// ignore Decl and Examples
189+
got.Decl = nil
190+
got.Examples = nil
191+
if !(got.Doc == want.Doc &&
192+
got.Name == want.Name &&
193+
got.Recv == want.Recv &&
194+
got.Orig == want.Orig &&
195+
got.Level == want.Level) {
196+
t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
197+
}
198+
}
199+
200+
compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
201+
compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
202+
if got.Name != want.Name {
203+
t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
204+
} else {
205+
compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
206+
compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
207+
}
208+
})
209+
}
210+
211+
func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
212+
if len(got) != len(want) {
213+
t.Errorf("%s: got %d, want %d", name, len(got), len(want))
214+
}
215+
for i := 0; i < len(got) && i < len(want); i++ {
216+
compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
217+
}
218+
}
219+
220+
const funcsTestFile = `
221+
package funcs
222+
223+
func F() {}
224+
225+
type S1 struct {
226+
S2 // embedded, exported
227+
s3 // embedded, unexported
228+
}
229+
230+
func NewS1() S1 {return S1{} }
231+
func NewS1p() *S1 { return &S1{} }
232+
233+
func (S1) M1() {}
234+
func (r S1) M2() {}
235+
func(S1) m3() {} // unexported not shown
236+
func (*S1) P1() {} // pointer receiver
237+
238+
type S2 int
239+
func (S2) M3() {} // shown on S2
240+
241+
type s3 int
242+
func (s3) M4() {} // shown on S1
243+
244+
type G1[T any] struct {
245+
*s3
246+
}
247+
248+
func NewG1[T any]() G1[T] { return G1[T]{} }
249+
250+
func (G1[T]) MG1() {}
251+
func (*G1[U]) MG2() {}
252+
253+
type G2[T, U any] struct {}
254+
255+
func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
256+
257+
func (G2[T, U]) MG3() {}
258+
func (*G2[A, B]) MG4() {}
259+
260+
261+
`
262+
263+
var funcsPackage = &Package{
264+
Funcs: []*Func{{Name: "F"}},
265+
Types: []*Type{
266+
{
267+
Name: "G1",
268+
Funcs: []*Func{{Name: "NewG1"}},
269+
Methods: []*Func{
270+
{Name: "M4", Recv: "G1", // TODO: synthesize a param for G1?
271+
Orig: "s3", Level: 1},
272+
{Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
273+
{Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
274+
},
275+
},
276+
{
277+
Name: "G2",
278+
Funcs: []*Func{{Name: "NewG2"}},
279+
Methods: []*Func{
280+
{Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
281+
{Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
282+
},
283+
},
284+
{
285+
Name: "S1",
286+
Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
287+
Methods: []*Func{
288+
{Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
289+
{Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
290+
{Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
291+
{Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
292+
},
293+
},
294+
{
295+
Name: "S2",
296+
Methods: []*Func{
297+
{Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
298+
},
299+
},
300+
},
301+
}

src/go/doc/reader.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
package doc
66

77
import (
8+
"fmt"
89
"go/ast"
910
"go/token"
1011
"internal/lazyregexp"
1112
"sort"
1213
"strconv"
14+
"strings"
1315
)
1416

1517
// ----------------------------------------------------------------------------
@@ -22,19 +24,43 @@ import (
2224
//
2325
type methodSet map[string]*Func
2426

25-
// recvString returns a string representation of recv of the
26-
// form "T", "*T", or "BADRECV" (if not a proper receiver type).
27+
// recvString returns a string representation of recv of the form "T", "*T",
28+
// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).
2729
//
2830
func recvString(recv ast.Expr) string {
2931
switch t := recv.(type) {
3032
case *ast.Ident:
3133
return t.Name
3234
case *ast.StarExpr:
3335
return "*" + recvString(t.X)
36+
case *ast.IndexExpr:
37+
// Generic type with one parameter.
38+
return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))
39+
case *ast.IndexListExpr:
40+
// Generic type with multiple parameters.
41+
if len(t.Indices) > 0 {
42+
var b strings.Builder
43+
b.WriteString(recvString(t.X))
44+
b.WriteByte('[')
45+
b.WriteString(recvParam(t.Indices[0]))
46+
for _, e := range t.Indices[1:] {
47+
b.WriteString(", ")
48+
b.WriteString(recvParam(e))
49+
}
50+
b.WriteByte(']')
51+
return b.String()
52+
}
3453
}
3554
return "BADRECV"
3655
}
3756

57+
func recvParam(p ast.Expr) string {
58+
if id, ok := p.(*ast.Ident); ok {
59+
return id.Name
60+
}
61+
return "BADPARAM"
62+
}
63+
3864
// set creates the corresponding Func for f and adds it to mset.
3965
// If there are multiple f's with the same name, set keeps the first
4066
// one with documentation; conflicts are ignored. The boolean

0 commit comments

Comments
 (0)