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)
+ }
+ }
+}