internal/lsp/source: add inferred types to generic function hover

As an experiment, this CL introduces the first gopls feature that is
specific to generics: enriching function hover information with inferred
types. This is done with no additional gating on build constraints by
using the new internal/typeparams package.

The marker tests are updated to allow tests that rely on type parameters
being enabled.

Change-Id: Ic627d64b61a6211389196814edd0abe1484491eb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/317452
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index d7447b2..6bd4135 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -26,6 +26,7 @@
 	"golang.org/x/tools/internal/memoize"
 	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
 	errors "golang.org/x/xerrors"
 )
@@ -343,7 +344,6 @@
 			}
 		}
 	}
-
 	// If this is a replaced module in the workspace, the version is
 	// meaningless, and we don't want clients to access it.
 	if m.module != nil {
@@ -447,6 +447,7 @@
 		},
 		typesSizes: m.typesSizes,
 	}
+	typeparams.InitInferred(pkg.typesInfo)
 
 	for _, gf := range pkg.m.goFiles {
 		// In the presence of line directives, we may need to report errors in
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index ee38dd7..be2bfe2 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -19,6 +19,7 @@
 
 	"golang.org/x/tools/internal/event"
 	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/typeparams"
 	errors "golang.org/x/xerrors"
 )
 
@@ -125,10 +126,10 @@
 				break
 			}
 		}
-		h.Signature = objectString(x, i.qf)
+		h.Signature = objectString(x, i.qf, i.Inferred)
 	}
 	if obj := i.Declaration.obj; obj != nil {
-		h.SingleLine = objectString(obj, i.qf)
+		h.SingleLine = objectString(obj, i.qf, nil)
 	}
 	obj := i.Declaration.obj
 	if obj == nil {
@@ -237,7 +238,21 @@
 
 // objectString is a wrapper around the types.ObjectString function.
 // It handles adding more information to the object string.
-func objectString(obj types.Object, qf types.Qualifier) string {
+func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
+	// If the signature type was inferred, prefer the preferred signature with a
+	// comment showing the generic signature.
+	if sig, _ := obj.Type().(*types.Signature); sig != nil && len(typeparams.ForSignature(sig)) > 0 && inferred != nil {
+		obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred)
+		str := types.ObjectString(obj2, qf)
+		// Try to avoid overly long lines.
+		if len(str) > 60 {
+			str += "\n"
+		} else {
+			str += " "
+		}
+		str += "// " + types.TypeString(sig, qf)
+		return str
+	}
 	str := types.ObjectString(obj, qf)
 	switch obj := obj.(type) {
 	case *types.Const:
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 9fb3daa..ff86eaa 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -18,6 +18,7 @@
 	"golang.org/x/tools/internal/event"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/typeparams"
 	errors "golang.org/x/xerrors"
 )
 
@@ -32,6 +33,8 @@
 		Object types.Object
 	}
 
+	Inferred *types.Signature
+
 	Declaration Declaration
 
 	ident *ast.Ident
@@ -292,6 +295,8 @@
 		return result, nil
 	}
 
+	result.Inferred = inferredSignature(pkg.GetTypesInfo(), path)
+
 	result.Type.Object = typeToObject(typ)
 	if result.Type.Object != nil {
 		// Identifiers with the type "error" are a special case with no position.
@@ -337,6 +342,52 @@
 	return nil, nil
 }
 
+// inferredSignature determines the resolved non-generic signature for an
+// identifier with a generic signature that is the operand of an IndexExpr or
+// CallExpr.
+//
+// If no such signature exists, it returns nil.
+func inferredSignature(info *types.Info, path []ast.Node) *types.Signature {
+	if len(path) < 2 {
+		return nil
+	}
+	// There are four ways in which a signature may be resolved:
+	//  1. It has no explicit type arguments, but the CallExpr can be fully
+	//     inferred from function arguments.
+	//  2. It has full type arguments, and the IndexExpr has a non-generic type.
+	//  3. For a partially instantiated IndexExpr representing a function-valued
+	//     expression (i.e. not part of a CallExpr), type arguments may be
+	//     inferred using constraint type inference.
+	//  4. For a partially instantiated IndexExpr that is part of a CallExpr,
+	//     type arguments may be inferred using both constraint type inference
+	//     and function argument inference.
+	//
+	// These branches are handled below.
+	switch n := path[1].(type) {
+	case *ast.CallExpr:
+		_, sig := typeparams.GetInferred(info, n)
+		return sig
+	case *ast.IndexExpr:
+		// If the IndexExpr is fully instantiated, we consider that 'inference' for
+		// gopls' purposes.
+		sig, _ := info.TypeOf(n).(*types.Signature)
+		if sig != nil && len(typeparams.ForSignature(sig)) == 0 {
+			return sig
+		}
+		_, sig = typeparams.GetInferred(info, n)
+		if sig != nil {
+			return sig
+		}
+		if len(path) >= 2 {
+			if call, _ := path[2].(*ast.CallExpr); call != nil {
+				_, sig := typeparams.GetInferred(info, call)
+				return sig
+			}
+		}
+	}
+	return nil
+}
+
 func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
 	for _, n := range path {
 		switch n := n.(type) {
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
index 2f7d8de..c268293 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/godef/a/a.go.golden
@@ -76,6 +76,42 @@
 [`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)
 -- aPackage-hover --
 Package a is a package for testing go to definition\.
+-- declBlockA-hover --
+```go
+type a struct {
+	x string
+}
+```
+
+1st type declaration block
+-- declBlockB-hover --
+```go
+type b struct{}
+```
+
+b has a comment
+-- declBlockC-hover --
+```go
+type c struct {
+	f string
+}
+```
+
+c is a struct
+-- declBlockD-hover --
+```go
+type d string
+```
+
+3rd type declaration block
+-- declBlockE-hover --
+```go
+type e struct {
+	f float64
+}
+```
+
+e has a comment
 -- err-definition --
 godef/a/a.go:33:6-9: defined here as ```go
 var err error
@@ -148,39 +184,3 @@
 ```
 
 z is a variable too\.
--- declBlockA-hover --
-```go
-type a struct {
-	x string
-}
-```
-
-1st type declaration block
--- declBlockB-hover --
-```go
-type b struct{}
-```
-
-b has a comment
--- declBlockC-hover --
-```go
-type c struct {
-	f string
-}
-```
-
-c is a struct
--- declBlockD-hover --
-```go
-type d string
-```
-
-3rd type declaration block
--- declBlockE-hover --
-```go
-type e struct {
-	f float64
-}
-```
-
-e has a comment
diff --git a/internal/lsp/testdata/godef/a/h.go.golden b/internal/lsp/testdata/godef/a/h.go.golden
index 71f78e1..3525d4c 100644
--- a/internal/lsp/testdata/godef/a/h.go.golden
+++ b/internal/lsp/testdata/godef/a/h.go.golden
@@ -1,39 +1,3 @@
--- nestedNumber-hover --
-```go
-field number int64
-```
-
-nested number
--- nestedString-hover --
-```go
-field str string
-```
-
-nested string
--- nestedMap-hover --
-```go
-field m map[string]float64
-```
-
-nested map
--- structA-hover --
-```go
-field a int
-```
-
-a field
--- structB-hover --
-```go
-field b struct{c int}
-```
-
-b nested struct
--- structC-hover --
-```go
-field c int
-```
-
-c field of nested struct
 -- arrD-hover --
 ```go
 field d int
@@ -86,12 +50,60 @@
 ```
 
 X value field
+-- nestedMap-hover --
+```go
+field m map[string]float64
+```
+
+nested map
+-- nestedNumber-hover --
+```go
+field number int64
+```
+
+nested number
+-- nestedString-hover --
+```go
+field str string
+```
+
+nested string
 -- openMethod-hover --
 ```go
 func (interface).open() error
 ```
 
 open method comment
+-- returnX-hover --
+```go
+field x int
+```
+
+X coord
+-- returnY-hover --
+```go
+field y int
+```
+
+Y coord
+-- structA-hover --
+```go
+field a int
+```
+
+a field
+-- structB-hover --
+```go
+field b struct{c int}
+```
+
+b nested struct
+-- structC-hover --
+```go
+field c int
+```
+
+c field of nested struct
 -- testDescription-hover --
 ```go
 field desc string
@@ -122,15 +134,3 @@
 ```
 
 expected test value
--- returnX-hover --
-```go
-field x int
-```
-
-X coord
--- returnY-hover --
-```go
-field y int
-```
-
-Y coord
\ No newline at end of file
diff --git a/internal/lsp/testdata/godef/b/h.go.golden b/internal/lsp/testdata/godef/b/h.go.golden
index 85f0404..b854dd4 100644
--- a/internal/lsp/testdata/godef/b/h.go.golden
+++ b/internal/lsp/testdata/godef/b/h.go.golden
@@ -1,12 +1,12 @@
+-- AStuff-hover --
+```go
+func AStuff()
+```
+
+[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
 -- AVariable-hover --
 ```go
 var _ A
 ```
 
 variable of type a\.A
--- AStuff-hover --
-```go
-func AStuff()
-```
-
-[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
\ No newline at end of file
diff --git a/internal/lsp/testdata/godef/infer_generics/inferred.go b/internal/lsp/testdata/godef/infer_generics/inferred.go
new file mode 100644
index 0000000..78abf27
--- /dev/null
+++ b/internal/lsp/testdata/godef/infer_generics/inferred.go
@@ -0,0 +1,12 @@
+package inferred
+
+func app[S interface{ ~[]E }, E any](s S, e E) S {
+	return append(s, e)
+}
+
+func _() {
+	_ = app[[]int]             //@mark(constrInfer, "app"),hover("app", constrInfer)
+	_ = app[[]int, int]        //@mark(instance, "app"),hover("app", instance)
+	_ = app[[]int]([]int{}, 0) //@mark(partialInfer, "app"),hover("app", partialInfer)
+	_ = app([]int{}, 0)        //@mark(argInfer, "app"),hover("app", argInfer)
+}
diff --git a/internal/lsp/testdata/godef/infer_generics/inferred.go.golden b/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
new file mode 100644
index 0000000..2dd97d9
--- /dev/null
+++ b/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
@@ -0,0 +1,20 @@
+-- argInfer-hover --
+```go
+func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
+```
+-- constrInf-hover --
+```go
+func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
+```
+-- constrInfer-hover --
+```go
+func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
+```
+-- instance-hover --
+```go
+func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
+```
+-- partialInfer-hover --
+```go
+func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
+```
diff --git a/internal/lsp/testdata/summary_generics.txt.golden b/internal/lsp/testdata/summary_generics.txt.golden
new file mode 100644
index 0000000..152f38d
--- /dev/null
+++ b/internal/lsp/testdata/summary_generics.txt.golden
@@ -0,0 +1,29 @@
+-- summary --
+CallHierarchyCount = 2
+CodeLensCount = 5
+CompletionsCount = 265
+CompletionSnippetCount = 103
+UnimportedCompletionsCount = 5
+DeepCompletionsCount = 5
+FuzzyCompletionsCount = 8
+RankedCompletionsCount = 163
+CaseSensitiveCompletionsCount = 4
+DiagnosticsCount = 37
+FoldingRangesCount = 2
+FormatCount = 6
+ImportCount = 8
+SemanticTokenCount = 3
+SuggestedFixCount = 40
+FunctionExtractionCount = 18
+DefinitionsCount = 99
+TypeDefinitionsCount = 18
+HighlightsCount = 69
+ReferencesCount = 25
+RenamesCount = 33
+PrepareRenamesCount = 7
+SymbolsCount = 5
+WorkspaceSymbolsCount = 20
+SignaturesCount = 32
+LinksCount = 7
+ImplementationsCount = 14
+
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 53861e0..f942ced 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -32,6 +32,7 @@
 	"golang.org/x/tools/internal/lsp/source/completion"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/testenv"
+	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/txtar"
 )
 
@@ -39,10 +40,17 @@
 	overlayFileSuffix = ".overlay"
 	goldenFileSuffix  = ".golden"
 	inFileSuffix      = ".in"
-	summaryFile       = "summary.txt"
 	testModule        = "golang.org/x/tools/internal/lsp"
 )
 
+var summaryFile = "summary.txt"
+
+func init() {
+	if typeparams.Enabled {
+		summaryFile = "summary_generics.txt"
+	}
+}
+
 var UpdateGolden = flag.Bool("golden", false, "Update golden files")
 
 type CallHierarchy map[span.Span]*CallHierarchyResult
@@ -322,6 +330,14 @@
 	}
 
 	files := packagestest.MustCopyFileTree(dir)
+	// Prune test cases that exercise generics.
+	if !typeparams.Enabled {
+		for name := range files {
+			if strings.Contains(name, "_generics") {
+				delete(files, name)
+			}
+		}
+	}
 	overlays := map[string][]byte{}
 	for fragment, operation := range files {
 		if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {