go/ssa: determine whether a type is parameterized.

Adds a type visitor to ssa that determines whether a type is parameterized or not.
Adapted from go/types/infer.go.

Updates golang/go#48525

Change-Id: I06285c6753ba8c5aab2dfe418556fdcf2b2f9437
Reviewed-on: https://go-review.googlesource.com/c/tools/+/386317
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Tim King <taking@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Trust: Tim King <taking@google.com>
diff --git a/go/ssa/parameterized.go b/go/ssa/parameterized.go
new file mode 100644
index 0000000..956718c
--- /dev/null
+++ b/go/ssa/parameterized.go
@@ -0,0 +1,113 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssa
+
+import (
+	"go/types"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+// tpWalker walks over types looking for parameterized types.
+//
+// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy.
+type tpWalker struct {
+	seen map[types.Type]bool
+}
+
+// isParameterized returns true when typ contains any type parameters.
+func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
+	// NOTE: Adapted from go/types/infer.go. Try to keep in sync.
+
+	// detect cycles
+	if x, ok := w.seen[typ]; ok {
+		return x
+	}
+	w.seen[typ] = false
+	defer func() {
+		w.seen[typ] = res
+	}()
+
+	switch t := typ.(type) {
+	case nil, *types.Basic: // TODO(gri) should nil be handled here?
+		break
+
+	case *types.Array:
+		return w.isParameterized(t.Elem())
+
+	case *types.Slice:
+		return w.isParameterized(t.Elem())
+
+	case *types.Struct:
+		for i, n := 0, t.NumFields(); i < n; i++ {
+			if w.isParameterized(t.Field(i).Type()) {
+				return true
+			}
+		}
+
+	case *types.Pointer:
+		return w.isParameterized(t.Elem())
+
+	case *types.Tuple:
+		n := t.Len()
+		for i := 0; i < n; i++ {
+			if w.isParameterized(t.At(i).Type()) {
+				return true
+			}
+		}
+
+	case *types.Signature:
+		// t.tparams may not be nil if we are looking at a signature
+		// of a generic function type (or an interface method) that is
+		// part of the type we're testing. We don't care about these type
+		// parameters.
+		// Similarly, the receiver of a method may declare (rather then
+		// use) type parameters, we don't care about those either.
+		// Thus, we only need to look at the input and result parameters.
+		return w.isParameterized(t.Params()) || w.isParameterized(t.Results())
+
+	case *types.Interface:
+		for i, n := 0, t.NumMethods(); i < n; i++ {
+			if w.isParameterized(t.Method(i).Type()) {
+				return true
+			}
+		}
+		terms, err := typeparams.InterfaceTermSet(t)
+		if err != nil {
+			panic(err)
+		}
+		for _, term := range terms {
+			if w.isParameterized(term.Type()) {
+				return true
+			}
+		}
+
+	case *types.Map:
+		return w.isParameterized(t.Key()) || w.isParameterized(t.Elem())
+
+	case *types.Chan:
+		return w.isParameterized(t.Elem())
+
+	case *types.Named:
+		args := typeparams.NamedTypeArgs(t)
+		// TODO(taking): this does not match go/types/infer.go. Check with rfindley.
+		if params := typeparams.ForNamed(t); params.Len() > args.Len() {
+			return true
+		}
+		for i, n := 0, args.Len(); i < n; i++ {
+			if w.isParameterized(args.At(i)) {
+				return true
+			}
+		}
+
+	case *typeparams.TypeParam:
+		return true
+
+	default:
+		panic(t) // unreachable
+	}
+
+	return false
+}
diff --git a/go/ssa/parameterized_test.go b/go/ssa/parameterized_test.go
new file mode 100644
index 0000000..64c9125
--- /dev/null
+++ b/go/ssa/parameterized_test.go
@@ -0,0 +1,80 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssa
+
+import (
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"testing"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+func TestIsParameterized(t *testing.T) {
+	if !typeparams.Enabled {
+		return
+	}
+
+	const source = `
+package P
+type A int
+func (A) f()
+func (*A) g()
+
+type fer interface { f() }
+
+func Apply[T fer](x T) T {
+	x.f()
+	return x
+}
+
+type V[T any] []T
+func (v *V[T]) Push(x T) { *v = append(*v, x) }
+`
+
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "hello.go", source, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var conf types.Config
+	pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range []struct {
+		expr string // type expression
+		want bool   // expected isParameterized value
+	}{
+		{"A", false},
+		{"*A", false},
+		{"error", false},
+		{"*error", false},
+		{"struct{A}", false},
+		{"*struct{A}", false},
+		{"fer", false},
+		{"Apply", true},
+		{"Apply[A]", false},
+		{"V", true},
+		{"V[A]", false},
+		{"*V[A]", false},
+		{"(*V[A]).Push", false},
+	} {
+		tv, err := types.Eval(fset, pkg, 0, test.expr)
+		if err != nil {
+			t.Errorf("Eval(%s) failed: %v", test.expr, err)
+		}
+
+		param := tpWalker{seen: make(map[types.Type]bool)}
+		if got := param.isParameterized(tv.Type); got != test.want {
+			t.Logf("Eval(%s) returned the type %s", test.expr, tv.Type)
+			t.Errorf("isParameterized(%s) = %v, want %v", test.expr, got, test.want)
+		}
+	}
+}