go/types, types2: add missing generic test to Checker.Instantiate

Checker.Instantiate failed with an assertion failure if the
'validate' argument was set and the type to be instantiated
was not generic. Report an error instead.

We may not fail with a panic when 'validate' is not set (as we
may do for an incorrect type argument count) because we cannot
easily detect this case eagerly (accessing the type parameters
or named types eagerly may cause loading of the named type and
lead to deadlock, e.g. when instantiating types during import).

Fixes #72978.

Change-Id: I0a2ebe7fa3953c60fa57dce3734c329970419057
Reviewed-on: https://go-review.googlesource.com/c/go/+/790040
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go
index c3372e9..6cd1ed3 100644
--- a/src/cmd/compile/internal/types2/instantiate.go
+++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -21,7 +21,7 @@
 }
 
 // Instantiate instantiates the type orig with the given type arguments targs.
-// orig must be an *Alias, *Named, or *Signature type. If there is no error,
+// orig must be a generic *Alias, *Named, or *Signature type. If there is no error,
 // the resulting Type is an instantiated type of the same kind (*Alias, *Named
 // or *Signature, respectively).
 //
@@ -36,16 +36,16 @@
 // signatures will yield different instances. The use of a shared context does
 // not guarantee that identical instances are deduplicated in all cases.
 //
-// If validate is set, Instantiate verifies that the number of type arguments
-// and parameters match, and that the type arguments satisfy their respective
-// type constraints. If verification fails, the resulting error may wrap an
-// *ArgumentError indicating which type argument did not satisfy its type parameter
-// constraint, and why.
+// If validate is set, Instantiate verifies that the type orig is in fact generic,
+// that the number of type arguments and parameters match, and that the type arguments
+// satisfy their respective type constraints.
+// If verification fails, the resulting error may wrap an *ArgumentError indicating
+// which type argument did not satisfy its type parameter constraint, and why.
 //
-// If validate is not set, Instantiate does not verify the type argument count
-// or whether the type arguments satisfy their constraints. Instantiate is
-// guaranteed to not return an error, but may panic. Specifically, for
-// *Signature types, Instantiate will panic immediately if the type argument
+// If validate is not set, Instantiate does not check if orig is generic, verify the
+// type argument count, or check whether the type arguments satisfy their constraints.
+// Instantiate is guaranteed to not return an error, but may panic. Specifically,
+// for *Signature types, Instantiate will panic immediately if the type argument
 // count is incorrect; for *Named types, a panic may occur later inside the
 // *Named API.
 func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) {
@@ -57,7 +57,9 @@
 
 	if validate {
 		tparams := orig_.TypeParams().list()
-		assert(len(tparams) > 0)
+		if len(tparams) == 0 {
+			return nil, fmt.Errorf("%s is not generic", orig_)
+		}
 		if len(targs) != len(tparams) {
 			return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams))
 		}
diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go
index ef5f535..be7d88a 100644
--- a/src/cmd/compile/internal/types2/issues_test.go
+++ b/src/cmd/compile/internal/types2/issues_test.go
@@ -1191,3 +1191,34 @@
 		t.Fatalf("unexpected type for {x}: %s", tv.Type)
 	}
 }
+
+func TestIssue72978(t *testing.T) {
+	const src = `
+package p
+
+type (
+	genericG[T, U any] struct { x T; y U }
+	G1 genericG[int, string]
+	G2 = G1
+)
+
+func genericF[T, U any]() {}
+var f = genericF[string, float64]
+`
+
+	pkg := mustTypecheck(src, nil, nil)
+	for _, name := range []string{"G1", "G2", "f"} {
+		func() {
+			typ := pkg.Scope().Lookup(name).Type()
+			_, err := Instantiate(nil, typ, []Type{Typ[Bool], Typ[Int]}, true)
+			if err == nil {
+				t.Errorf("%s[bool, int]: got no error", name)
+				return
+			}
+			want := fmt.Sprintf("%s is not generic", typ)
+			if err.Error() != want {
+				t.Errorf("%s[bool, int]: got %q, want %q", name, err.Error(), want)
+			}
+		}()
+	}
+}
diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go
index 4851a6f..c059092 100644
--- a/src/go/types/instantiate.go
+++ b/src/go/types/instantiate.go
@@ -24,7 +24,7 @@
 }
 
 // Instantiate instantiates the type orig with the given type arguments targs.
-// orig must be an *Alias, *Named, or *Signature type. If there is no error,
+// orig must be a generic *Alias, *Named, or *Signature type. If there is no error,
 // the resulting Type is an instantiated type of the same kind (*Alias, *Named
 // or *Signature, respectively).
 //
@@ -39,16 +39,16 @@
 // signatures will yield different instances. The use of a shared context does
 // not guarantee that identical instances are deduplicated in all cases.
 //
-// If validate is set, Instantiate verifies that the number of type arguments
-// and parameters match, and that the type arguments satisfy their respective
-// type constraints. If verification fails, the resulting error may wrap an
-// *ArgumentError indicating which type argument did not satisfy its type parameter
-// constraint, and why.
+// If validate is set, Instantiate verifies that the type orig is in fact generic,
+// that the number of type arguments and parameters match, and that the type arguments
+// satisfy their respective type constraints.
+// If verification fails, the resulting error may wrap an *ArgumentError indicating
+// which type argument did not satisfy its type parameter constraint, and why.
 //
-// If validate is not set, Instantiate does not verify the type argument count
-// or whether the type arguments satisfy their constraints. Instantiate is
-// guaranteed to not return an error, but may panic. Specifically, for
-// *Signature types, Instantiate will panic immediately if the type argument
+// If validate is not set, Instantiate does not check if orig is generic, verify the
+// type argument count, or check whether the type arguments satisfy their constraints.
+// Instantiate is guaranteed to not return an error, but may panic. Specifically,
+// for *Signature types, Instantiate will panic immediately if the type argument
 // count is incorrect; for *Named types, a panic may occur later inside the
 // *Named API.
 func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) {
@@ -60,7 +60,9 @@
 
 	if validate {
 		tparams := orig_.TypeParams().list()
-		assert(len(tparams) > 0)
+		if len(tparams) == 0 {
+			return nil, fmt.Errorf("%s is not generic", orig_)
+		}
 		if len(targs) != len(tparams) {
 			return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams))
 		}
diff --git a/src/go/types/issues_test.go b/src/go/types/issues_test.go
index 2403d40..51110cd 100644
--- a/src/go/types/issues_test.go
+++ b/src/go/types/issues_test.go
@@ -1205,3 +1205,34 @@
 		t.Fatalf("unexpected type for {x}: %s", tv.Type)
 	}
 }
+
+func TestIssue72978(t *testing.T) {
+	const src = `
+package p
+
+type (
+	genericG[T, U any] struct { x T; y U }
+	G1 genericG[int, string]
+	G2 = G1
+)
+
+func genericF[T, U any]() {}
+var f = genericF[string, float64]
+`
+
+	pkg := mustTypecheck(src, nil, nil)
+	for _, name := range []string{"G1", "G2", "f"} {
+		func() {
+			typ := pkg.Scope().Lookup(name).Type()
+			_, err := Instantiate(nil, typ, []Type{Typ[Bool], Typ[Int]}, true)
+			if err == nil {
+				t.Errorf("%s[bool, int]: got no error", name)
+				return
+			}
+			want := fmt.Sprintf("%s is not generic", typ)
+			if err.Error() != want {
+				t.Errorf("%s[bool, int]: got %q, want %q", name, err.Error(), want)
+			}
+		}()
+	}
+}