godoc: handle type parameters correctly in LinkifyText

LinkifyText should not generate links to global declarations for
identifiers that are type parameters.

Because the syntactic resolver does not record the declaration for type
parameters declared in the method receiver (see
https://golang.org/issue/50956) a name lookup is used as a workaround.
This is fine here because it is only applied to undeclared indentifiers
and LinkifyText is only ever called on a single declaration at a time,
not on a full AST.

Updates golang/go#50717

Change-Id: I32f2203ce80c060ee18ca8b964a2d5fd80f41957
Reviewed-on: https://go-review.googlesource.com/c/tools/+/382714
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Alessandro Arzilli <alessandro.arzilli@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Trust: Peter Weinberger <pjw@google.com>
This commit is contained in:
aarzilli 2022-02-01 18:45:42 +01:00 committed by Robert Findley
parent c0b9fb59f7
commit c6fca02004
2 changed files with 99 additions and 2 deletions

View File

@ -10,6 +10,8 @@ import (
"go/token"
"strings"
"testing"
"golang.org/x/tools/internal/typeparams"
)
func TestPkgLinkFunc(t *testing.T) {
@ -368,3 +370,58 @@ func TestFilterOutBuildAnnotations(t *testing.T) {
t.Errorf("filterOutBuildAnnotations should not remove non-build tag comment")
}
}
func TestLinkifyGenerics(t *testing.T) {
if !typeparams.Enabled {
t.Skip("type params are not enabled at this Go version")
}
got := linkifySource(t, []byte(`
package foo
type T struct {
field *T
}
type ParametricStruct[T any] struct {
field *T
}
func F1[T any](arg T) { }
func F2(arg T) { }
func (*ParametricStruct[T]) M(arg T) { }
func (*T) M(arg T) { }
type ParametricStruct2[T1, T2 any] struct {
a T1
b T2
}
func (*ParametricStruct2[T1, T2]) M(a T1, b T2) { }
`))
want := `type T struct {
<span id="T.field"></span>field *<a href="#T">T</a>
}
type ParametricStruct[T <a href="/pkg/builtin/#any">any</a>] struct {
<span id="ParametricStruct.field"></span>field *T
}
func F1[T <a href="/pkg/builtin/#any">any</a>](arg T) {}
func F2(arg <a href="#T">T</a>) {}
func (*<a href="#ParametricStruct">ParametricStruct</a>[T]) M(arg T) {}
func (*<a href="#T">T</a>) M(arg <a href="#T">T</a>) {}
type ParametricStruct2[T1, T2 <a href="/pkg/builtin/#any">any</a>] struct {
<span id="ParametricStruct2.a"></span>a T1
<span id="ParametricStruct2.b"></span>b T2
}
func (*<a href="#ParametricStruct2">ParametricStruct2</a>[T1, T2]) M(a T1, b T2) {}`
if got != want {
t.Errorf("got: %s\n\nwant: %s\n", got, want)
}
}

View File

@ -17,6 +17,8 @@ import (
"go/token"
"io"
"strconv"
"golang.org/x/tools/internal/typeparams"
)
// LinkifyText HTML-escapes source text and writes it to w.
@ -89,6 +91,8 @@ func linksFor(node ast.Node) (links []link) {
// their ast.Ident nodes are visited.
linkMap := make(map[*ast.Ident]link)
typeParams := make(map[string]bool)
ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.Field:
@ -105,6 +109,24 @@ func linksFor(node ast.Node) (links []link) {
}
case *ast.FuncDecl:
linkMap[n.Name] = link{}
if n.Recv != nil {
recv := n.Recv.List[0].Type
if r, isstar := recv.(*ast.StarExpr); isstar {
recv = r.X
}
switch x := recv.(type) {
case *ast.IndexExpr:
if ident, _ := x.Index.(*ast.Ident); ident != nil {
typeParams[ident.Name] = true
}
case *typeparams.IndexListExpr:
for _, index := range x.Indices {
if ident, _ := index.(*ast.Ident); ident != nil {
typeParams[ident.Name] = true
}
}
}
}
case *ast.TypeSpec:
linkMap[n.Name] = link{}
case *ast.AssignStmt:
@ -183,8 +205,26 @@ func linksFor(node ast.Node) (links []link) {
links = append(links, l)
} else {
l := link{name: n.Name}
if n.Obj == nil && doc.IsPredeclared(n.Name) {
l.path = builtinPkgPath
if n.Obj == nil {
if doc.IsPredeclared(n.Name) {
l.path = builtinPkgPath
} else {
if typeParams[n.Name] {
// If a type parameter was declared then do not generate a link.
// Doing this is necessary because type parameter identifiers do not
// have their Decl recorded sometimes, see
// https://golang.org/issue/50956.
l = link{}
}
}
} else {
if n.Obj.Kind == ast.Typ {
if _, isfield := n.Obj.Decl.(*ast.Field); isfield {
// If an identifier is a type declared in a field assume it is a type
// parameter and do not generate a link.
l = link{}
}
}
}
links = append(links, l)
}