From 6d8748d961ee05d8c0619b9e5bfb204f9e2e801a Mon Sep 17 00:00:00 2001 From: aarzilli Date: Tue, 4 Jan 2022 13:16:06 +0100 Subject: [PATCH] godoc: fix addNames for generics The type of a function receiver can now also be a IndexExpr. Fixes golang/go#50413 Change-Id: I5ac7bee8ea6b594be00d00c7fed2e2a9fe260b10 Reviewed-on: https://go-review.googlesource.com/c/tools/+/373857 Trust: Than McIntosh Run-TryBot: Alessandro Arzilli gopls-CI: kokoro TryBot-Result: Gopher Robot Reviewed-by: Robert Findley --- server.go | 16 +++++-- server_test.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 48e8d95..9c5d556 100644 --- a/server.go +++ b/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/server_test.go b/server_test.go index 0d48e9f..d6cc923 100644 --- a/server_test.go +++ b/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") + } +}