diff --git a/godoc/server.go b/godoc/server.go
index 48e8d957ad..9c5d556b8e 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -30,6 +30,7 @@ import (
"golang.org/x/tools/godoc/analysis"
"golang.org/x/tools/godoc/util"
"golang.org/x/tools/godoc/vfs"
+ "golang.org/x/tools/internal/typeparams"
)
// handlerServer is a migration from an old godoc http Handler type.
@@ -462,12 +463,19 @@ func addNames(names map[string]bool, decl ast.Decl) {
case *ast.FuncDecl:
name := d.Name.Name
if d.Recv != nil {
+ r := d.Recv.List[0].Type
+ if rr, isstar := r.(*ast.StarExpr); isstar {
+ r = rr.X
+ }
+
var typeName string
- switch r := d.Recv.List[0].Type.(type) {
- case *ast.StarExpr:
- typeName = r.X.(*ast.Ident).Name
+ switch x := r.(type) {
case *ast.Ident:
- typeName = r.Name
+ typeName = x.Name
+ case *ast.IndexExpr:
+ typeName = x.X.(*ast.Ident).Name
+ case *typeparams.IndexListExpr:
+ typeName = x.X.(*ast.Ident).Name
}
name = typeName + "_" + name
}
diff --git a/godoc/server_test.go b/godoc/server_test.go
index 0d48e9f04b..d6cc923c6c 100644
--- a/godoc/server_test.go
+++ b/godoc/server_test.go
@@ -5,14 +5,17 @@
package godoc
import (
+ "go/doc"
"net/http"
"net/http/httptest"
"net/url"
+ "sort"
"strings"
"testing"
"text/template"
"golang.org/x/tools/godoc/vfs/mapfs"
+ "golang.org/x/tools/internal/typeparams"
)
// TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files,
@@ -128,3 +131,115 @@ func TestMarkdown(t *testing.T) {
testServeBody(t, p, "/doc/test.html", "bold")
testServeBody(t, p, "/doc/test2.html", "template")
}
+
+func TestGenerics(t *testing.T) {
+ if !typeparams.Enabled {
+ t.Skip("type params are not enabled at this Go version")
+ }
+
+ c := NewCorpus(mapfs.New(map[string]string{
+ "blah/blah.go": `package blah
+
+var A AStruct[int]
+
+type AStruct[T any] struct {
+ A string
+ X T
+}
+
+func (a *AStruct[T]) Method() T {
+ return a.X
+}
+
+func (a AStruct[T]) NonPointerMethod() T {
+ return a.X
+}
+
+func NewAStruct[T any](arg T) *AStruct[T] {
+ return &AStruct[T]{ X: arg }
+}
+
+type NonGenericStruct struct {
+ B int
+}
+
+func (b *NonGenericStruct) NonGenericMethod() int {
+ return b.B
+}
+
+func NewNonGenericStruct(arg int) *NonGenericStruct {
+ return &NonGenericStruct{arg}
+}
+
+type Pair[K, V any] struct {
+ K K
+ V V
+}
+
+func (p Pair[K, V]) Apply(kf func(K) K, vf func(V) V) Pair[K, V] {
+ return &Pair{ K: kf(p.K), V: vf(p.V) }
+}
+
+func (p *Pair[K, V]) Set(k K, v V) {
+ p.K = k
+ p.V = v
+}
+
+func NewPair[K, V any](k K, v V) Pair[K, V] {
+ return Pair[K, V]{ k, v }
+}
+`}))
+
+ srv := &handlerServer{
+ p: &Presentation{
+ Corpus: c,
+ },
+ c: c,
+ }
+ pInfo := srv.GetPageInfo("/blah/", "", NoFiltering, "linux", "amd64")
+ t.Logf("%v\n", pInfo)
+
+ findType := func(name string) *doc.Type {
+ for _, typ := range pInfo.PDoc.Types {
+ if typ.Name == name {
+ return typ
+ }
+ }
+ return nil
+ }
+
+ assertFuncs := func(typ *doc.Type, typFuncs []*doc.Func, funcs ...string) {
+ typfuncs := make([]string, len(typFuncs))
+ for i := range typFuncs {
+ typfuncs[i] = typFuncs[i].Name
+ }
+ sort.Strings(typfuncs)
+ sort.Strings(funcs)
+ if len(typfuncs) != len(funcs) {
+ t.Errorf("function mismatch for type %q, got: %q, want: %q", typ.Name, typfuncs, funcs)
+ return
+ }
+ for i := range funcs {
+ if funcs[i] != typfuncs[i] {
+ t.Errorf("function mismatch for type %q: got: %q, want: %q", typ.Name, typfuncs, funcs)
+ return
+ }
+ }
+ }
+
+ aStructType := findType("AStruct")
+ assertFuncs(aStructType, aStructType.Funcs, "NewAStruct")
+ assertFuncs(aStructType, aStructType.Methods, "Method", "NonPointerMethod")
+
+ nonGenericStructType := findType("NonGenericStruct")
+ assertFuncs(nonGenericStructType, nonGenericStructType.Funcs, "NewNonGenericStruct")
+ assertFuncs(nonGenericStructType, nonGenericStructType.Methods, "NonGenericMethod")
+
+ pairType := findType("Pair")
+ assertFuncs(pairType, pairType.Funcs, "NewPair")
+ assertFuncs(pairType, pairType.Methods, "Apply", "Set")
+
+ if len(pInfo.PDoc.Funcs) > 0 {
+ t.Errorf("unexpected functions in package documentation")
+ }
+}