summaryrefslogtreecommitdiffstats
path: root/typeparams
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:35:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:35:35 +0000
commit331441e10889478d5a88eeda5d74057904e5e384 (patch)
tree0e63feed70b3e3fa3bf3096bb7985d4a80425cb3 /typeparams
parentInitial commit. (diff)
downloadgolang-golang-x-exp-331441e10889478d5a88eeda5d74057904e5e384.tar.xz
golang-golang-x-exp-331441e10889478d5a88eeda5d74057904e5e384.zip
Adding upstream version 0.0~git20231006.7918f67.upstream/0.0_git20231006.7918f67upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'typeparams')
-rw-r--r--typeparams/common.go182
-rw-r--r--typeparams/common_test.go238
-rw-r--r--typeparams/copytermlist.go99
-rw-r--r--typeparams/example/README.md898
-rw-r--r--typeparams/example/findtypeparams/main.go156
-rw-r--r--typeparams/example/generic-go-types.md448
-rw-r--r--typeparams/example/genericmethods/main.go122
-rw-r--r--typeparams/example/implicit/main.go52
-rw-r--r--typeparams/example/instantiation/main.go158
-rw-r--r--typeparams/example/interfaces/main.go138
-rw-r--r--typeparams/example/methoddecls/main.go137
-rw-r--r--typeparams/example/predicates/main.go114
-rw-r--r--typeparams/example/typesets/main.go71
-rw-r--r--typeparams/go.mod3
-rw-r--r--typeparams/normalize.go200
-rw-r--r--typeparams/normalize_test.go104
-rw-r--r--typeparams/termlist.go172
-rw-r--r--typeparams/typeparams_go117.go201
-rw-r--r--typeparams/typeparams_go118.go147
-rw-r--r--typeparams/typeparams_test.go137
-rw-r--r--typeparams/typeterm.go169
21 files changed, 3946 insertions, 0 deletions
diff --git a/typeparams/common.go b/typeparams/common.go
new file mode 100644
index 0000000..7f867cf
--- /dev/null
+++ b/typeparams/common.go
@@ -0,0 +1,182 @@
+// Copyright 2021 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 typeparams contains common utilities for writing tools that interact
+// with generic Go code, as introduced with Go 1.18.
+//
+// Many of the types and functions in this package are proxies for the new APIs
+// introduced in the standard library with Go 1.18. For example, the
+// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
+// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
+// versions older than 1.18 these helpers are implemented as stubs, allowing
+// users of this package to write code that handles generic constructs inline,
+// even if the Go version being used to compile does not support generics.
+//
+// Additionally, this package contains common utilities for working with the
+// new generic constructs, to supplement the standard library APIs. Notably,
+// the NormalTerms API computes a minimal representation of the structural
+// restrictions on a type parameter. In the future, these supplemental APIs may
+// be available in the standard library..
+package typeparams
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+)
+
+// Enabled reports whether type parameters are enabled in the current build
+// environment.
+func Enabled() bool {
+ return enabled
+}
+
+// UnpackIndexExpr extracts data from AST nodes that represent index
+// expressions.
+//
+// For an ast.IndexExpr, the resulting indices slice will contain exactly one
+// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
+// number of index expressions.
+//
+// For nodes that don't represent index expressions, the first return value of
+// UnpackIndexExpr will be nil.
+func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
+ switch e := n.(type) {
+ case *ast.IndexExpr:
+ return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
+ case *IndexListExpr:
+ return e.X, e.Lbrack, e.Indices, e.Rbrack
+ }
+ return nil, token.NoPos, nil, token.NoPos
+}
+
+// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
+// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
+// will panic.
+func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
+ switch len(indices) {
+ case 0:
+ panic("empty indices")
+ case 1:
+ return &ast.IndexExpr{
+ X: x,
+ Lbrack: lbrack,
+ Index: indices[0],
+ Rbrack: rbrack,
+ }
+ default:
+ return &IndexListExpr{
+ X: x,
+ Lbrack: lbrack,
+ Indices: indices,
+ Rbrack: rbrack,
+ }
+ }
+}
+
+// IsTypeParam reports whether t is a type parameter.
+func IsTypeParam(t types.Type) bool {
+ _, ok := t.(*TypeParam)
+ return ok
+}
+
+// OriginMethod returns the origin method associated with the method fn. For
+// methods on a non-generic receiver base type, this is just fn. However, for
+// methods with a generic receiver, OriginMethod returns the corresponding
+// method in the method set of the origin type.
+//
+// As a special case, if fn is not a method (has no receiver), OriginMethod
+// returns fn.
+func OriginMethod(fn *types.Func) *types.Func {
+ recv := fn.Type().(*types.Signature).Recv()
+ if recv == nil {
+ return fn
+ }
+ base := recv.Type()
+ p, isPtr := base.(*types.Pointer)
+ if isPtr {
+ base = p.Elem()
+ }
+ named, isNamed := base.(*types.Named)
+ if !isNamed {
+ // Receiver is a *types.Interface.
+ return fn
+ }
+ if ForNamed(named).Len() == 0 {
+ // Receiver base has no type parameters, so we can avoid the lookup below.
+ return fn
+ }
+ orig := NamedTypeOrigin(named)
+ gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
+ return gfn.(*types.Func)
+}
+
+// GenericAssignableTo is a generalization of types.AssignableTo that
+// implements the following rule for uninstantiated generic types:
+//
+// If V and T are generic named types, then V is considered assignable to T if,
+// for every possible instantation of V[A_1, ..., A_N], the instantiation
+// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
+//
+// If T has structural constraints, they must be satisfied by V.
+//
+// For example, consider the following type declarations:
+//
+// type Interface[T any] interface {
+// Accept(T)
+// }
+//
+// type Container[T any] struct {
+// Element T
+// }
+//
+// func (c Container[T]) Accept(t T) { c.Element = t }
+//
+// In this case, GenericAssignableTo reports that instantiations of Container
+// are assignable to the corresponding instantiation of Interface.
+func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
+ // If V and T are not both named, or do not have matching non-empty type
+ // parameter lists, fall back on types.AssignableTo.
+
+ VN, Vnamed := V.(*types.Named)
+ TN, Tnamed := T.(*types.Named)
+ if !Vnamed || !Tnamed {
+ return types.AssignableTo(V, T)
+ }
+
+ vtparams := ForNamed(VN)
+ ttparams := ForNamed(TN)
+ if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
+ return types.AssignableTo(V, T)
+ }
+
+ // V and T have the same (non-zero) number of type params. Instantiate both
+ // with the type parameters of V. This must always succeed for V, and will
+ // succeed for T if and only if the type set of each type parameter of V is a
+ // subset of the type set of the corresponding type parameter of T, meaning
+ // that every instantiation of V corresponds to a valid instantiation of T.
+
+ // Minor optimization: ensure we share a context across the two
+ // instantiations below.
+ if ctxt == nil {
+ ctxt = NewContext()
+ }
+
+ var targs []types.Type
+ for i := 0; i < vtparams.Len(); i++ {
+ targs = append(targs, vtparams.At(i))
+ }
+
+ vinst, err := Instantiate(ctxt, V, targs, true)
+ if err != nil {
+ panic("type parameters should satisfy their own constraints")
+ }
+
+ tinst, err := Instantiate(ctxt, T, targs, true)
+ if err != nil {
+ return false
+ }
+
+ return types.AssignableTo(vinst, tinst)
+}
diff --git a/typeparams/common_test.go b/typeparams/common_test.go
new file mode 100644
index 0000000..5dd5c3f
--- /dev/null
+++ b/typeparams/common_test.go
@@ -0,0 +1,238 @@
+// Copyright 2021 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 typeparams_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "testing"
+
+ . "golang.org/x/exp/typeparams"
+)
+
+func TestGetIndexExprData(t *testing.T) {
+ x := &ast.Ident{}
+ i := &ast.Ident{}
+
+ want := &IndexListExpr{X: x, Lbrack: 1, Indices: []ast.Expr{i}, Rbrack: 2}
+ tests := map[ast.Node]bool{
+ &ast.IndexExpr{X: x, Lbrack: 1, Index: i, Rbrack: 2}: true,
+ want: true,
+ &ast.Ident{}: false,
+ }
+
+ for n, isIndexExpr := range tests {
+ X, lbrack, indices, rbrack := UnpackIndexExpr(n)
+ if got := X != nil; got != isIndexExpr {
+ t.Errorf("UnpackIndexExpr(%v) = %v, _, _, _; want nil: %t", n, x, !isIndexExpr)
+ }
+ if X == nil {
+ continue
+ }
+ if X != x || lbrack != 1 || indices[0] != i || rbrack != 2 {
+ t.Errorf("UnpackIndexExprData(%v) = %v, %v, %v, %v; want %+v", n, x, lbrack, indices, rbrack, want)
+ }
+ }
+}
+
+func SkipIfNotEnabled(t *testing.T) {
+ if !Enabled() {
+ t.Skip("type parameters are not enabled")
+ }
+}
+
+func TestOriginMethodRecursive(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ src := `package p
+
+type N[A any] int
+
+func (r N[B]) m() { r.m(); r.n() }
+
+func (r *N[C]) n() { }
+`
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ info := types.Info{
+ Defs: make(map[*ast.Ident]types.Object),
+ Uses: make(map[*ast.Ident]types.Object),
+ }
+ var conf types.Config
+ if _, err := conf.Check("p", fset, []*ast.File{f}, &info); err != nil {
+ t.Fatal(err)
+ }
+
+ // Collect objects from types.Info.
+ var m, n *types.Func // the 'origin' methods in Info.Defs
+ var mm, mn *types.Func // the methods used in the body of m
+
+ for _, decl := range f.Decls {
+ fdecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ def := info.Defs[fdecl.Name].(*types.Func)
+ switch fdecl.Name.Name {
+ case "m":
+ m = def
+ ast.Inspect(fdecl.Body, func(n ast.Node) bool {
+ if call, ok := n.(*ast.CallExpr); ok {
+ sel := call.Fun.(*ast.SelectorExpr)
+ use := info.Uses[sel.Sel].(*types.Func)
+ switch sel.Sel.Name {
+ case "m":
+ mm = use
+ case "n":
+ mn = use
+ }
+ }
+ return true
+ })
+ case "n":
+ n = def
+ }
+ }
+
+ tests := []struct {
+ name string
+ input, want *types.Func
+ }{
+ {"declared m", m, m},
+ {"declared n", n, n},
+ {"used m", mm, m},
+ {"used n", mn, n},
+ }
+
+ for _, test := range tests {
+ if got := OriginMethod(test.input); got != test.want {
+ t.Errorf("OriginMethod(%q) = %v, want %v", test.name, test.input, test.want)
+ }
+ }
+}
+
+func TestOriginMethodUses(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ tests := []string{
+ `type T interface { m() }; func _(t T) { t.m() }`,
+ `type T[P any] interface { m() P }; func _[A any](t T[A]) { t.m() }`,
+ `type T[P any] interface { m() P }; func _(t T[int]) { t.m() }`,
+ `type T[P any] int; func (r T[A]) m() { r.m() }`,
+ `type T[P any] int; func (r *T[A]) m() { r.m() }`,
+ `type T[P any] int; func (r *T[A]) m() {}; func _(t T[int]) { t.m() }`,
+ `type T[P any] int; func (r *T[A]) m() {}; func _[A any](t T[A]) { t.m() }`,
+ }
+
+ for _, src := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", "package p; "+src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ info := types.Info{
+ Uses: make(map[*ast.Ident]types.Object),
+ }
+ var conf types.Config
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, &info)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ T := pkg.Scope().Lookup("T").Type()
+ obj, _, _ := types.LookupFieldOrMethod(T, true, pkg, "m")
+ m := obj.(*types.Func)
+
+ ast.Inspect(f, func(n ast.Node) bool {
+ if call, ok := n.(*ast.CallExpr); ok {
+ sel := call.Fun.(*ast.SelectorExpr)
+ use := info.Uses[sel.Sel].(*types.Func)
+ orig := OriginMethod(use)
+ if orig != m {
+ t.Errorf("%s:\nUses[%v] = %v, want %v", src, types.ExprString(sel), use, m)
+ }
+ }
+ return true
+ })
+ }
+}
+
+func TestGenericAssignableTo(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ tests := []struct {
+ src string
+ want bool
+ }{
+ // The inciting issue: golang/go#50887.
+ {`
+ type T[P any] interface {
+ Accept(P)
+ }
+
+ type V[Q any] struct {
+ Element Q
+ }
+
+ func (c V[Q]) Accept(q Q) { c.Element = q }
+ `, true},
+
+ // Various permutations on constraints and signatures.
+ {`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false},
+ {`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false},
+ {`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false},
+
+ // Various structural restrictions on T.
+ {`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false},
+
+ // Various recursive constraints.
+ {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
+ {`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+
+ // In this test case, we reverse the type parameters in the signature of V.A
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
+ // It would be nice to return true here: V can only be instantiated with
+ // [int, int], so the identity of the type parameters should not matter.
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
+ }
+
+ for _, test := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0)
+ if err != nil {
+ t.Fatalf("%s:\n%v", test.src, err)
+ }
+ var conf types.Config
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ t.Fatalf("%s:\n%v", test.src, err)
+ }
+
+ V := pkg.Scope().Lookup("V").Type()
+ T := pkg.Scope().Lookup("T").Type()
+
+ if types.AssignableTo(V, T) {
+ t.Fatal("AssignableTo")
+ }
+
+ if got := GenericAssignableTo(nil, V, T); got != test.want {
+ t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want)
+ }
+ }
+}
diff --git a/typeparams/copytermlist.go b/typeparams/copytermlist.go
new file mode 100644
index 0000000..c67dc17
--- /dev/null
+++ b/typeparams/copytermlist.go
@@ -0,0 +1,99 @@
+// Copyright 2021 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.
+
+//go:build ignore
+
+// copytermlist.go copies the term list algorithm from GOROOT/src/go/types.
+// Run it with:
+// GO111MODULE=off go run copytermlist.go
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func main() {
+ if err := doCopy(); err != nil {
+ fmt.Fprintf(os.Stderr, "error copying from go/types: %v", err)
+ os.Exit(1)
+ }
+}
+
+func doCopy() error {
+ dir := filepath.Join(runtime.GOROOT(), "src", "go", "types")
+ for _, name := range []string{"typeterm.go", "termlist.go"} {
+ path := filepath.Join(dir, name)
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+ file.Name.Name = "typeparams"
+ file.Doc = &ast.CommentGroup{List: []*ast.Comment{&ast.Comment{Text: "DO NOT MODIFY"}}}
+ var needImport bool
+ selectorType := reflect.TypeOf((*ast.SelectorExpr)(nil))
+ astutil.Apply(file, func(c *astutil.Cursor) bool {
+ if id, _ := c.Node().(*ast.Ident); id != nil {
+ // Check if this ident should be qualified with types. For simplicity,
+ // assume the copied files do not themselves contain any exported
+ // symbols.
+
+ // As a simple heuristic, just verify that the ident may be replaced by
+ // a selector.
+ if !token.IsExported(id.Name) {
+ return false
+ }
+ v := reflect.TypeOf(c.Parent()).Elem() // ast nodes are all pointers
+ field, ok := v.FieldByName(c.Name())
+ if !ok {
+ panic("missing field")
+ }
+ t := field.Type
+ if c.Index() > 0 { // => t is a slice
+ t = t.Elem()
+ }
+ if !selectorType.AssignableTo(t) {
+ return false
+ }
+ needImport = true
+ c.Replace(&ast.SelectorExpr{
+ X: ast.NewIdent("types"),
+ Sel: ast.NewIdent(id.Name),
+ })
+ }
+ return true
+ }, nil)
+ if needImport {
+ astutil.AddImport(fset, file, "go/types")
+ }
+
+ var b bytes.Buffer
+ if err := format.Node(&b, fset, file); err != nil {
+ return err
+ }
+
+ // Hack in the 'generated' byline.
+ content := b.String()
+ header := "// Code generated by copytermlist.go DO NOT EDIT.\n\npackage typeparams"
+ content = strings.Replace(content, "package typeparams", header, 1)
+
+ if err := os.WriteFile(name, []byte(content), 0644); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/typeparams/example/README.md b/typeparams/example/README.md
new file mode 100644
index 0000000..afe5bfb
--- /dev/null
+++ b/typeparams/example/README.md
@@ -0,0 +1,898 @@
+<!-- Autogenerated by weave; DO NOT EDIT -->
+<!-- To regenerate the readme, run: -->
+<!-- go run golang.org/x/example/gotypes@latest generic-go-types.md -->
+
+# Updating tools to support type parameters.
+
+This guide is maintained by Rob Findley (`rfindley@google.com`).
+
+**status**: this document is currently a rough-draft. See [golang/go#50447](https://go.dev/issues/50447) for more details.
+
+1. [Who should read this guide](#who-should-read-this-guide)
+1. [Introduction](#introduction)
+1. [Summary of new language features and their APIs](#summary-of-new-language-features-and-their-apis)
+1. [Examples](#examples)
+ 1. [Generic types: type parameters](#generic-types:-type-parameters)
+ 1. [Constraint Interfaces](#constraint-interfaces)
+ 1. [Instantiation](#instantiation)
+ 1. [Generic types continued: method sets and predicates](#generic-types-continued:-method-sets-and-predicates)
+1. [Updating tools while building at older Go versions](#updating-tools-while-building-at-older-go-versions)
+1. [Further help](#further-help)
+
+# Who should read this guide
+
+Read this guide if you are a tool author seeking to update your tools to
+support generics Go code. Generics introduce significant new complexity to the
+Go type system, because types can now be _parameterized_. While the
+fundamentals of the `go/types` APIs remain the same, some previously valid
+assumptions no longer hold. For example:
+
+ - Type declarations need not correspond 1:1 with the types they define.
+ - Interfaces are no longer determined entirely by their method set.
+ - The set of concrete types implementing `types.Type` has grown to include
+ `types.TypeParam` and `types.Union`.
+
+# Introduction
+
+With Go 1.18, Go now supports generic programming via type parameters. This
+document is a guide for tool authors that want to update their tools to support
+the new language constructs.
+
+This guide assumes knowledge of the language changes to support generics. See
+the following references for more information:
+
+- The [original proposal](https://go.dev/issue/43651) for type parameters.
+- The [addendum for type sets](https://go.dev/issue/45346).
+- The [latest language specfication](https://tip.golang.org/ref/spec) (still in-progress as of 2021-01-11).
+- The proposals for new APIs in
+ [go/token and go/ast](https://go.dev/issue/47781), and in
+ [go/types](https://go.dev/issue/47916).
+
+It also assumes knowledge of `go/ast` and `go/types`. If you're just getting
+started,
+[x/example/gotypes](https://github.com/golang/example/tree/master/gotypes) is
+a great introduction (and was the inspiration for this guide).
+
+# Summary of new language features and their APIs
+
+The introduction of of generic features appears as a large change to the
+language, but a high level introduces only a few new concepts. We can break
+down our discussion into the following three broad categories: generic types,
+constraint interfaces, and instantiation. In each category below, the relevant
+new APIs are listed (some constructors and getters/setters may be elided where
+they are trivial):
+
+**Generic types**. Types and functions may be _generic_, meaning their
+declaration may have a non-empty _type parameter list_, as in
+`type List[T any] ...` or `func f[T1, T2 any]() { ... }`. Type parameter lists
+define placeholder types (_type parameters_), scoped to the declaration, which
+may be substituted by any type satisfying their corresponding _constraint
+interface_ to _instantiate_ a new type or function.
+
+Generic types may have methods, which declare `receiver type parameters` via
+their receiver type expression: `func (r T[P1, ..., PN]) method(...) (...)
+{...}`.
+
+_New APIs_:
+ - The field `ast.TypeSpec.TypeParams` holds the type parameter list syntax for
+ type declarations.
+ - The field `ast.FuncType.TypeParams` holds the type parameter list syntax for
+ function declarations.
+ - The type `types.TypeParam` is a `types.Type` representing a type parameter.
+ On this type, the `Constraint` and `SetConstraint` methods allow
+ getting/setting the constraint, the `Index` method returns the numeric index
+ of the type parameter in the type parameter list that declares it, and the
+ `Obj` method returns the object in the scope a for the type parameter (a
+ `types.TypeName`). Generic type declarations have a new `*types.Scope` for
+ type parameter declarations.
+ - The type `types.TypeParamList` holds a list of type parameters.
+ - The method `types.Named.TypeParams` returns the type parameters for a type
+ declaration.
+ - The method `types.Named.SetTypeParams` sets type parameters on a defined
+ type.
+ - The function `types.NewSignatureType` creates a new (possibly generic)
+ signature type.
+ - The method `types.Signature.RecvTypeParams` returns the receiver type
+ parameters for a method.
+ - The method `types.Signature.TypeParams` returns the type parameters for
+ a function.
+
+**Constraint Interfaces**: type parameter constraints are interfaces, expressed
+by an interface type expression. Interfaces that are only used in constraint
+position are permitted new embedded elements composed of tilde expressions
+(`~T`) and unions (`A | B | ~C`). The new builtin interface type `comparable`
+is implemented by types for which `==` and `!=` are valid (note that interfaces
+must be statically comparable in this case, i.e., each type in the interface's
+type set must be comparable). As a special case, the `interface` keyword may be
+omitted from constraint expressions if it may be implied (in which case we say
+the interface is _implicit_).
+
+_New APIs_:
+ - The constant `token.TILDE` is used to represent tilde expressions as an
+ `ast.UnaryExpr`.
+ - Union expressions are represented as an `ast.BinaryExpr` using `|`. This
+ means that `ast.BinaryExpr` may now be both a type and value expression.
+ - The method `types.Interface.IsImplicit` reports whether the `interface`
+ keyword was elided from this interface.
+ - The method `types.Interface.MarkImplicit` marks an interface as being
+ implicit.
+ - The method `types.Interface.IsComparable` reports whether every type in an
+ interface's type set is comparable.
+ - The method `types.Interface.IsMethodSet` reports whether an interface is
+ defined entirely by its methods (has no _specific types_).
+ - The type `types.Union` is a type that represents an embedded union
+ expression in an interface. May only appear as an embedded element in
+ interfaces.
+ - The type `types.Term` represents a (possibly tilde) term of a union.
+
+**Instantiation**: generic types and functions may be _instantiated_ to create
+non-generic types and functions by providing _type arguments_ (`var x T[int]`).
+Function type arguments may be _inferred_ via function arguments, or via
+type parameter constraints.
+
+_New APIs_:
+ - The type `ast.IndexListExpr` holds index expressions with multiple indices,
+ as in instantiation expressions with multiple type arguments or in receivers
+ declaring multiple type parameters.
+ - The function `types.Instantiate` instantiates a generic type with type arguments.
+ - The type `types.Context` is an opaque instantiation context that may be
+ shared to reduce duplicate instances.
+ - The field `types.Config.Context` holds a shared `Context` to use for
+ instantiation while type-checking.
+ - The type `types.TypeList` holds a list of types.
+ - The type `types.ArgumentError` holds an error associated with a specific
+ type argument index. Used to represent instantiation errors.
+ - The field `types.Info.Instances` maps instantiated identifiers to information
+ about the resulting type instance.
+ - The type `types.Instance` holds information about a type or function
+ instance.
+ - The method `types.Named.TypeArgs` reports the type arguments used to
+ instantiate a named type.
+
+# Examples
+
+The following examples demonstrate the new APIs, and discuss their properties.
+All examples are runnable, contained in subdirectories of the directory holding
+this README.
+
+## Generic types: type parameters
+
+We say that a type is _generic_ if it has type parameters but no type
+arguments. This section explains how we can inspect generic types with the new
+`go/types` APIs.
+
+### Type parameter lists
+
+Suppose we want to understand the generic library below, which defines a generic
+`Pair`, a constraint interface `Constraint`, and a generic function `MakePair`.
+
+```
+package main
+
+type Constraint interface {
+ Value() any
+}
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func MakePair[L, R Constraint](l L, r R) Pair[L, R] {
+ return Pair[L, R]{l, r}
+}
+```
+
+We can use the new `TypeParams` fields in `ast.TypeSpec` and `ast.FuncType` to
+access the type parameter list. From there, we can access type parameter types
+in at least three ways:
+ - by looking up type parameter definitions in `types.Info`
+ - by calling `TypeParams()` on `types.Named` or `types.Signature`
+ - by looking up type parameter objects in the declaration scope. Note that
+ there now may be a scope associated with an `ast.TypeSpec` node.
+
+```
+func PrintTypeParams(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Scopes: make(map[ast.Node]*types.Scope),
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ _, err := conf.Check("hello", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+
+ // For convenience, we can use ast.Inspect to find the nodes we want to
+ // investigate.
+ ast.Inspect(file, func(n ast.Node) bool {
+ var name *ast.Ident // the name of the generic object, or nil
+ var tparamFields *ast.FieldList // the list of type parameter fields
+ var tparams *types.TypeParamList // the list of type parameter types
+ var scopeNode ast.Node // the node associated with the declaration scope
+
+ switch n := n.(type) {
+ case *ast.TypeSpec:
+ name = n.Name
+ tparamFields = n.TypeParams
+ tparams = info.Defs[name].Type().(*types.Named).TypeParams()
+ scopeNode = n
+ case *ast.FuncDecl:
+ name = n.Name
+ tparamFields = n.Type.TypeParams
+ tparams = info.Defs[name].Type().(*types.Signature).TypeParams()
+ scopeNode = n.Type
+ default:
+ // Not a type or function declaration.
+ return true
+ }
+
+ // Option 1: find type parameters by looking at their declaring field list.
+ if tparamFields != nil {
+ fmt.Printf("%s has a type parameter field list with %d fields\n", name.Name, tparamFields.NumFields())
+ for _, field := range tparamFields.List {
+ for _, name := range field.Names {
+ tparam := info.Defs[name]
+ fmt.Printf(" field %s defines an object %q\n", name.Name, tparam)
+ }
+ }
+ } else {
+ fmt.Printf("%s does not have a type parameter list\n", name.Name)
+ }
+
+ // Option 2: find type parameters via the TypeParams() method on the
+ // generic type.
+ if tparams.Len() > 0 {
+ fmt.Printf("%s has %d type parameters:\n", name.Name, tparams.Len())
+ for i := 0; i < tparams.Len(); i++ {
+ tparam := tparams.At(i)
+ fmt.Printf(" %s has constraint %s\n", tparam, tparam.Constraint())
+ }
+ } else {
+ fmt.Printf("%s does not have type parameters\n", name.Name)
+ }
+
+ // Option 3: find type parameters by looking in the declaration scope.
+ scope, ok := info.Scopes[scopeNode]
+ if ok {
+ fmt.Printf("%s has a scope with %d objects:\n", name.Name, scope.Len())
+ for _, name := range scope.Names() {
+ fmt.Printf(" %s is a %T\n", name, scope.Lookup(name))
+ }
+ } else {
+ fmt.Printf("%s does not have a scope\n", name.Name)
+ }
+
+ return true
+ })
+ return nil
+}
+```
+
+This program produces the following output. Note that not every type spec has
+a scope.
+
+```
+> go run golang.org/x/tools/internal/typeparams/example/findtypeparams
+Constraint does not have a type parameter list
+Constraint does not have type parameters
+Constraint does not have a scope
+Pair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L any"
+ field R defines an object "type parameter R any"
+Pair has 2 type parameters:
+ L has constraint any
+ R has constraint any
+Pair has a scope with 2 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+MakePair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L hello.Constraint"
+ field R defines an object "type parameter R hello.Constraint"
+MakePair has 2 type parameters:
+ L has constraint hello.Constraint
+ R has constraint hello.Constraint
+MakePair has a scope with 4 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+ l is a *types.Var
+ r is a *types.Var
+```
+
+## Constraint Interfaces
+
+In order to allow operations on type parameters, Go 1.18 introduces the notion
+of [_type sets_](https://tip.golang.org/ref/spec#Interface_types), which is
+abstractly the set of types that implement an interface. This section discusses
+the new syntax for restrictions on interface type sets, and the APIs we can use
+to understand them.
+
+### New interface elements
+
+Consider the generic library below:
+
+```
+package p
+
+type Numeric interface{
+ ~int | ~float64 // etc...
+}
+
+func Square[N Numeric](n N) N {
+ return n*n
+}
+
+type Findable interface {
+ comparable
+}
+
+func Find[T Findable](s []T, v T) int {
+ for i, v2 := range s {
+ if v2 == v {
+ return i
+ }
+ }
+ return -1
+}
+```
+
+In this library, we can see a few new features added in Go 1.18. The first is
+the new syntax in the `Numeric` type: unions of tilde-terms, specifying that
+the numeric type may only be satisfied by types whose underlying type is `int`
+or `float64`.
+
+The `go/ast` package parses this new syntax as a combination of unary and
+binary expressions, which we can see using the following program:
+
+```
+func PrintNumericSyntax(fset *token.FileSet, file *ast.File) {
+ // node is the AST node corresponding to the declaration for "Numeric."
+ node := file.Scope.Lookup("Numeric").Decl.(*ast.TypeSpec)
+ // Find the embedded syntax node.
+ embedded := node.Type.(*ast.InterfaceType).Methods.List[0].Type
+ // Use go/ast's built-in Print function to inspect the parsed syntax.
+ ast.Print(fset, embedded)
+}
+```
+
+Output:
+
+```
+ 0 *ast.BinaryExpr {
+ 1 . X: *ast.UnaryExpr {
+ 2 . . OpPos: p.go:6:2
+ 3 . . Op: ~
+ 4 . . X: *ast.Ident {
+ 5 . . . NamePos: p.go:6:3
+ 6 . . . Name: "int"
+ 7 . . }
+ 8 . }
+ 9 . OpPos: p.go:6:7
+ 10 . Op: |
+ 11 . Y: *ast.UnaryExpr {
+ 12 . . OpPos: p.go:6:9
+ 13 . . Op: ~
+ 14 . . X: *ast.Ident {
+ 15 . . . NamePos: p.go:6:10
+ 16 . . . Name: "float64"
+ 17 . . }
+ 18 . }
+ 19 }
+```
+
+Once type-checked, these embedded expressions are represented using the new
+`types.Union` type, which flattens the expression into a list of `*types.Term`.
+We can also investigate two new methods of interface:
+`types.Interface.IsComparable`, which reports whether the type set of an
+interface is comparable, and `types.Interface.IsMethodSet`, which reports
+whether an interface is expressable using methods alone.
+
+```
+func PrintInterfaceTypes(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{}
+ pkg, err := conf.Check("hello", fset, []*ast.File{file}, nil)
+ if err != nil {
+ return err
+ }
+
+ PrintIface(pkg, "Numeric")
+ PrintIface(pkg, "Findable")
+
+ return nil
+}
+
+func PrintIface(pkg *types.Package, name string) {
+ obj := pkg.Scope().Lookup(name)
+ fmt.Println(obj)
+ iface := obj.Type().Underlying().(*types.Interface)
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ for i := 0; i < iface.NumMethods(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ fmt.Printf("\tIsComparable(): %t\n", iface.IsComparable())
+ fmt.Printf("\tIsMethodSet(): %t\n", iface.IsMethodSet())
+}
+```
+
+Output:
+
+```
+type hello.Numeric interface{~int|~float64}
+ embeded: ~int|~float64
+ IsComparable(): true
+ IsMethodSet(): false
+type hello.Findable interface{comparable}
+ embeded: comparable
+ IsComparable(): true
+ IsMethodSet(): false
+```
+
+The `Findable` type demonstrates another new feature of Go 1.18: the comparable
+built-in. Comparable is a special interface type, not expressable using
+ordinary Go syntax, whose type-set consists of all comparable types.
+
+### Implicit interfaces
+
+For interfaces that do not have methods, we can inline them in constraints and
+elide the `interface` keyword. In the example above, we could have done this
+for the `Square` function:
+
+```
+package p
+
+func Square[N ~int|~float64](n N) N {
+ return n*n
+}
+```
+
+In such cases, the `types.Interface.IsImplicit` method reports whether the
+interface type was implicit. This does not affect the behavior of the
+interface, but is captured for more accurate type strings:
+
+```
+func ShowImplicit(pkg *types.Package) {
+ Square := pkg.Scope().Lookup("Square").Type().(*types.Signature)
+ N := Square.TypeParams().At(0)
+ constraint := N.Constraint().(*types.Interface)
+ fmt.Println(constraint)
+ fmt.Println("IsImplicit:", constraint.IsImplicit())
+}
+```
+
+Output:
+
+```
+~int|~float64
+IsImplicit: true
+```
+
+The `types.Interface.MarkImplicit` method is used to mark interfaces as
+implicit by the importer.
+
+### Type sets
+
+The examples above demonstrate the new APIs for _accessing_ information about
+the new interface elements, but how do we understand
+[_type sets_](https://tip.golang.org/ref/spec#Interface_types), the new
+abstraction that these elements help define? Type sets may be arbitrarily
+complex, as in the following example:
+
+```
+package complex
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type D interface{ A|B; C }
+```
+
+Here, the type set of `D` simplifies to `~string|int`, but the current
+`go/types` APIs do not expose this information. This will likely be added to
+`go/types` in future versions of Go, but in the meantime we can use the
+`typeparams.NormalTerms` helper:
+
+```
+func PrintNormalTerms(pkg *types.Package) error {
+ D := pkg.Scope().Lookup("D").Type()
+ terms, err := typeparams.NormalTerms(D)
+ if err != nil {
+ return err
+ }
+ for i, term := range terms {
+ if i > 0 {
+ fmt.Print("|")
+ }
+ fmt.Print(term)
+ }
+ fmt.Println()
+ return nil
+}
+```
+
+which outputs:
+
+```
+~string|int
+```
+
+See the documentation for `typeparams.NormalTerms` for more information on how
+this calculation proceeds.
+
+## Instantiation
+
+We say that a type is _instantiated_ if it is created from a generic type by
+substituting type arguments for type parameters. Instantiation can occur via
+explicitly provided type arguments, as in the expression `T[A_1, ..., A_n]`, or
+implicitly, through type inference.. This section describes how to find and
+understand instantiated types.
+
+### Finding instantiated types
+
+Certain applications may find it useful to locate all instantiated types in
+a package. For this purpose, `go/types` provides a new `types.Info.Instances`
+field that maps instantiated identifiers to information about their instance.
+
+For example, consider the following code:
+
+```
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func Equal[L, R comparable](x, y Pair[L, R]) bool {
+ return x.left == y.left && x.right == y.right
+}
+
+var X Pair[int, string]
+var Y Pair[string, int]
+
+var E = Equal[int, string]
+```
+
+We can find instances by type-checking with the `types.Info.Instances` map
+initialized:
+
+```
+func CheckInstances(fset *token.FileSet, file *ast.File) (*types.Package, error) {
+ conf := types.Config{}
+ info := &types.Info{
+ Instances: make(map[*ast.Ident]types.Instance),
+ }
+ pkg, err := conf.Check("p", fset, []*ast.File{file}, info)
+ for id, inst := range info.Instances {
+ posn := fset.Position(id.Pos())
+ fmt.Printf("%s: %s instantiated with %s: %s\n", posn, id.Name, FormatTypeList(inst.TypeArgs), inst.Type)
+ }
+ return pkg, err
+}
+```
+
+Output:
+
+```
+hello.go:21:9: Equal instantiated with [int, string]: func(x p.Pair[int, string], y p.Pair[int, string]) bool
+hello.go:10:9: Pair instantiated with [L, _]: p.Pair[L, _]
+hello.go:14:34: Pair instantiated with [L, R]: p.Pair[L, R]
+hello.go:18:7: Pair instantiated with [int, string]: p.Pair[int, string]
+hello.go:19:7: Pair instantiated with [string, int]: p.Pair[string, int]
+```
+
+The `types.Instance` type provides information about the (possibly inferred)
+type arguments that were used to instantiate the generic type, and the
+resulting type. Notably, it does not include the _generic_ type that was
+instantiated, because this type can be found using `types.Info.Uses[id].Type()`
+(where `id` is the identifier node being instantiated).
+
+Note that the receiver type of method `Left` also appears in the `Instances`
+map. This may be counterintuitive -- more on this below.
+
+### Creating new instantiated types
+
+`go/types` also provides an API for creating type instances:
+`types.Instantiate`. This function accepts a generic type and type arguments,
+and returns an instantiated type (or an error). The resulting instance may be
+a newly constructed type, or a previously created instance with the same type
+identity. To facilitate the reuse of frequently used instances,
+`types.Instantiate` accepts a `types.Context` as its first argument, which
+records instances.
+
+If the final `validate` argument to `types.Instantiate` is set, the provided
+type arguments will be verified against their corresponding type parameter
+constraint; i.e., `types.Instantiate` will check that each type arguments
+implements the corresponding type parameter constraint. If a type arguments
+does not implement the respective constraint, the resulting error will wrap
+a new `ArgumentError` type indicating which type argument index was bad.
+
+```
+func Instantiate(pkg *types.Package) error {
+ Pair := pkg.Scope().Lookup("Pair").Type()
+ X := pkg.Scope().Lookup("X").Type()
+ Y := pkg.Scope().Lookup("Y").Type()
+
+ // X and Y have different types, because their type arguments are different.
+ Compare("X", "Y", X, Y)
+
+ // Create a shared context for the subsequent instantiations.
+ ctxt := types.NewContext()
+
+ // Instantiating with [int, string] yields an instance that is identical (but
+ // not equal) to X.
+ Int, String := types.Typ[types.Int], types.Typ[types.String]
+ inst1, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("X", "inst1", X, inst1)
+
+ // Instantiating again returns the same exact instance, because of the shared
+ // Context.
+ inst2, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("inst1", "inst2", inst1, inst2)
+
+ // Instantiating with 'any' is an error, because any is not comparable.
+ Any := types.Universe.Lookup("any").Type()
+ _, err := types.Instantiate(ctxt, Pair, []types.Type{Int, Any}, true)
+ var argErr *types.ArgumentError
+ if errors.As(err, &argErr) {
+ fmt.Printf("Argument %d: %v\n", argErr.Index, argErr.Err)
+ }
+
+ return nil
+}
+
+func Compare(leftName, rightName string, left, right types.Type) {
+ fmt.Printf("Identical(%s, %s) : %t\n", leftName, rightName, types.Identical(left, right))
+ fmt.Printf("%s == %s : %t\n\n", leftName, rightName, left == right)
+}
+```
+
+Output:
+
+```
+Identical(p.Pair[int, string], p.Pair[string, int]) : false
+p.Pair[int, string] == p.Pair[string, int] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : false
+
+Identical(p.Pair[string, int], p.Pair[int, string]) : false
+p.Pair[string, int] == p.Pair[int, string] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : true
+
+Argument 1: any does not implement comparable
+```
+
+### Using a shared context while type checking
+
+To share a common `types.Context` argument with a type-checking pass, set the
+new `types.Config.Context` field.
+
+## Generic types continued: method sets and predicates
+
+Generic types are fundamentally different from ordinary types, in that they may
+not be used without instantiation. In some senses they are not really types:
+the go spec defines [types](https://tip.golang.org/ref/spec#Types) as "a set of
+values, together with operations and methods", but uninstantiated generic types
+do not define a set of values. Rather, they define a set of _types_. In that
+sense, they are a "meta type", or a "type template" (disclaimer: I am using
+these terms imprecisely).
+
+However, for the purposes of `go/types` it is convenient to treat generic types
+as a `types.Type`. This section explains how generic types behave in existing
+`go/types` APIs.
+
+### Method Sets
+
+Methods on uninstantiated generic types are different from methods on an
+ordinary type. Consider that for an ordinary type `T`, the receiver base type
+of each method in its method set is `T`. However, this can't be the case for
+a generic type: generic types cannot be used without instantation, and neither
+can the type of the receiver variable. Instead, the receiver base type is an
+_instantiated_ type, instantiated with the method's receiver type parameters.
+
+This has some surprising consequences, which we observed in the section on
+instantiation above: for a generic type `G`, each of its methods will define
+a unique instantiation of `G`, as each method has distinct receiver type
+parameters.
+
+To see this, consider the following example:
+
+```
+package p
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+var IntPair Pair[int, int]
+```
+
+Let's inspect the method sets of the types in this library:
+
+```
+func PrintMethods(pkg *types.Package) {
+ // Look up *Named types in the package scope.
+ lookup := func(name string) *types.Named {
+ return pkg.Scope().Lookup(name).Type().(*types.Named)
+ }
+
+ Pair := lookup("Pair")
+ IntPair := lookup("IntPair")
+ PrintMethodSet("Pair", Pair)
+ PrintMethodSet("Pair[int, int]", IntPair)
+ LeftObj, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftRecvType := LeftObj.Type().(*types.Signature).Recv().Type()
+ PrintMethodSet("Pair[L, _]", LeftRecvType)
+}
+
+func PrintMethodSet(name string, typ types.Type) {
+ fmt.Println(name + ":")
+ methodSet := types.NewMethodSet(typ)
+ for i := 0; i < methodSet.Len(); i++ {
+ method := methodSet.At(i).Obj()
+ fmt.Println(method)
+ }
+ fmt.Println()
+}
+```
+
+Output:
+
+```
+Pair:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[_, R]).Right() R
+
+Pair[int, int]:
+func (p.Pair[int, int]).Left() int
+func (p.Pair[int, int]).Right() int
+
+Pair[L, _]:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[L, _]).Right() _
+```
+
+In this example, we can see that all of `Pair`, `Pair[int, int]`, and
+`Pair[L, _]` have distinct method sets, though the method set of `Pair` and
+`Pair[L, _]` intersect in the `Left` method.
+
+Only the objects in `Pair`'s method set are recorded in `types.Info.Defs`. To
+get back to this "canonical" method object, the `typeparams` package provides
+the `OriginMethod` helper:
+
+```
+func CompareOrigins(pkg *types.Package) {
+ Pair := pkg.Scope().Lookup("Pair").Type().(*types.Named)
+ IntPair := pkg.Scope().Lookup("IntPair").Type().(*types.Named)
+ Left, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftInt, _, _ := types.LookupFieldOrMethod(IntPair, false, pkg, "Left")
+
+ fmt.Println("Pair.Left == Pair[int, int].Left:", Left == LeftInt)
+ origin := typeparams.OriginMethod(LeftInt.(*types.Func))
+ fmt.Println("Pair.Left == OriginMethod(Pair[int, int].Left):", Left == origin)
+}
+```
+
+Output:
+
+```
+Pair.Left == Pair[int, int].Left: false
+Pair.Left == OriginMethod(Pair[int, int].Left): true
+```
+
+### Predicates
+
+Predicates on generic types are not defined by the spec. As a consequence,
+using e.g. `types.AssignableTo` with operands of generic types leads to an
+undefined result.
+
+The behavior of predicates on generic `*types.Named` types may generally be
+derived from the fact that type parameters bound to different names are
+different types. This means that most predicates involving generic types will
+return `false`.
+
+`*types.Signature` types are treated differently. Two signatures are considered
+identical if they are identical after substituting one's set of type parameters
+for the other's, including having identical type parameter constraints. This is
+analogous to the treatment of ordinary value parameters, whose names do not
+affect type identity.
+
+Consider the following code:
+
+```
+func OrdinaryPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ Mer = pkg.Scope().Lookup("Mer").Type()
+ F = pkg.Scope().Lookup("F").Type()
+ G = pkg.Scope().Lookup("G").Type()
+ H = pkg.Scope().Lookup("H").Type()
+ )
+
+ fmt.Println("AssignableTo(Pair, LeftRighter)", types.AssignableTo(Pair, LeftRighter))
+ fmt.Println("AssignableTo(Pair, Mer): ", types.AssignableTo(Pair, Mer))
+ fmt.Println("Identical(F, G)", types.Identical(F, G))
+ fmt.Println("Identical(F, H)", types.Identical(F, H))
+}
+```
+
+Output:
+
+```
+AssignableTo(Pair, LeftRighter) false
+AssignableTo(Pair, Mer): true
+Identical(F, G) true
+Identical(F, H) false
+```
+
+In this example, we see that despite their similarity the generic `Pair` type
+is not assignable to the generic `LeftRighter` type. We also see the rules for
+signature identity in practice.
+
+This begs the question: how does one ask questions about the relationship
+between generic types? In order to phrase such questions we need more
+information: how does one relate the type parameters of `Pair` to the type
+parameters of `LeftRighter`? Does it suffice for the predicate to hold for one
+element of the type sets, or must it hold for all elements of the type sets?
+
+We can use instantiation to answer some of these questions. In particular, by
+instantiating both `Pair` and `LeftRighter` with the type parameters of `Pair`,
+we can determine if, for all type arguments `[X, Y]` that are valid for `Pair`,
+`[X, Y]` are also valid type arguments of `LeftRighter`, and `Pair[X, Y]` is
+assignable to `LeftRighter[X, Y]`. The `typeparams.GenericAssignableTo`
+function implements exactly this predicate:
+
+```
+func GenericPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ )
+ fmt.Println("GenericAssignableTo(Pair, LeftRighter)", typeparams.GenericAssignableTo(nil, Pair, LeftRighter))
+}
+```
+
+Output:
+
+```
+GenericAssignableTo(Pair, LeftRighter) true
+```
+
+# Updating tools while building at older Go versions
+
+In the examples above, we can see how a lot of the new APIs integrate with
+existing usage of `go/ast` or `go/types`. However, most tools still need to
+build at older Go versions, and handling the new language constructs in-line
+will break builds at older Go versions.
+
+For this purpose, the `x/exp/typeparams` package provides functions and types
+that proxy the new APIs (with stub implementations at older Go versions).
+
+# Further help
+
+If you're working on updating a tool to support generics, and need help, please
+feel free to reach out for help in any of the following ways:
+ - By mailing the [golang-tools](https://groups.google.com/g/golang-tools) mailing list.
+ - Directly to me via email (`rfindley@google.com`).
+ - For bugs, you can [file an issue](https://github.com/golang/go/issues/new/choose).
diff --git a/typeparams/example/findtypeparams/main.go b/typeparams/example/findtypeparams/main.go
new file mode 100644
index 0000000..2026ba2
--- /dev/null
+++ b/typeparams/example/findtypeparams/main.go
@@ -0,0 +1,156 @@
+// Copyright 2021 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.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const hello = `
+//!+input
+package main
+
+type Constraint interface {
+ Value() any
+}
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func MakePair[L, R Constraint](l L, r R) Pair[L, R] {
+ return Pair[L, R]{l, r}
+}
+//!-input
+`
+
+// !+print
+func PrintTypeParams(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Scopes: make(map[ast.Node]*types.Scope),
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ _, err := conf.Check("hello", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+
+ // For convenience, we can use ast.Inspect to find the nodes we want to
+ // investigate.
+ ast.Inspect(file, func(n ast.Node) bool {
+ var name *ast.Ident // the name of the generic object, or nil
+ var tparamFields *ast.FieldList // the list of type parameter fields
+ var tparams *types.TypeParamList // the list of type parameter types
+ var scopeNode ast.Node // the node associated with the declaration scope
+
+ switch n := n.(type) {
+ case *ast.TypeSpec:
+ name = n.Name
+ tparamFields = n.TypeParams
+ tparams = info.Defs[name].Type().(*types.Named).TypeParams()
+ scopeNode = n
+ case *ast.FuncDecl:
+ name = n.Name
+ tparamFields = n.Type.TypeParams
+ tparams = info.Defs[name].Type().(*types.Signature).TypeParams()
+ scopeNode = n.Type
+ default:
+ // Not a type or function declaration.
+ return true
+ }
+
+ // Option 1: find type parameters by looking at their declaring field list.
+ if tparamFields != nil {
+ fmt.Printf("%s has a type parameter field list with %d fields\n", name.Name, tparamFields.NumFields())
+ for _, field := range tparamFields.List {
+ for _, name := range field.Names {
+ tparam := info.Defs[name]
+ fmt.Printf(" field %s defines an object %q\n", name.Name, tparam)
+ }
+ }
+ } else {
+ fmt.Printf("%s does not have a type parameter list\n", name.Name)
+ }
+
+ // Option 2: find type parameters via the TypeParams() method on the
+ // generic type.
+ if tparams.Len() > 0 {
+ fmt.Printf("%s has %d type parameters:\n", name.Name, tparams.Len())
+ for i := 0; i < tparams.Len(); i++ {
+ tparam := tparams.At(i)
+ fmt.Printf(" %s has constraint %s\n", tparam, tparam.Constraint())
+ }
+ } else {
+ fmt.Printf("%s does not have type parameters\n", name.Name)
+ }
+
+ // Option 3: find type parameters by looking in the declaration scope.
+ scope, ok := info.Scopes[scopeNode]
+ if ok {
+ fmt.Printf("%s has a scope with %d objects:\n", name.Name, scope.Len())
+ for _, name := range scope.Names() {
+ fmt.Printf(" %s is a %T\n", name, scope.Lookup(name))
+ }
+ } else {
+ fmt.Printf("%s does not have a scope\n", name.Name)
+ }
+
+ return true
+ })
+ return nil
+}
+
+//!-print
+
+/*
+//!+output
+> go run golang.org/x/tools/internal/typeparams/example/findtypeparams
+Constraint does not have a type parameter list
+Constraint does not have type parameters
+Constraint does not have a scope
+Pair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L any"
+ field R defines an object "type parameter R any"
+Pair has 2 type parameters:
+ L has constraint any
+ R has constraint any
+Pair has a scope with 2 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+MakePair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L hello.Constraint"
+ field R defines an object "type parameter R hello.Constraint"
+MakePair has 2 type parameters:
+ L has constraint hello.Constraint
+ R has constraint hello.Constraint
+MakePair has a scope with 4 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+ l is a *types.Var
+ r is a *types.Var
+//!-output
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", hello, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := PrintTypeParams(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/generic-go-types.md b/typeparams/example/generic-go-types.md
new file mode 100644
index 0000000..797dcd4
--- /dev/null
+++ b/typeparams/example/generic-go-types.md
@@ -0,0 +1,448 @@
+<!-- To regenerate the readme, run: -->
+<!-- go run golang.org/x/example/gotypes@latest generic-go-types.md -->
+
+# Updating tools to support type parameters.
+
+This guide is maintained by Rob Findley (`rfindley@google.com`).
+
+**status**: this document is currently a rough-draft. See [golang/go#50447](https://go.dev/issues/50447) for more details.
+
+%toc
+
+# Who should read this guide
+
+Read this guide if you are a tool author seeking to update your tools to
+support generics Go code. Generics introduce significant new complexity to the
+Go type system, because types can now be _parameterized_. While the
+fundamentals of the `go/types` APIs remain the same, some previously valid
+assumptions no longer hold. For example:
+
+ - Type declarations need not correspond 1:1 with the types they define.
+ - Interfaces are no longer determined entirely by their method set.
+ - The set of concrete types implementing `types.Type` has grown to include
+ `types.TypeParam` and `types.Union`.
+
+# Introduction
+
+With Go 1.18, Go now supports generic programming via type parameters. This
+document is a guide for tool authors that want to update their tools to support
+the new language constructs.
+
+This guide assumes knowledge of the language changes to support generics. See
+the following references for more information:
+
+- The [original proposal](https://go.dev/issue/43651) for type parameters.
+- The [addendum for type sets](https://go.dev/issue/45346).
+- The [latest language specfication](https://tip.golang.org/ref/spec) (still in-progress as of 2021-01-11).
+- The proposals for new APIs in
+ [go/token and go/ast](https://go.dev/issue/47781), and in
+ [go/types](https://go.dev/issue/47916).
+
+It also assumes knowledge of `go/ast` and `go/types`. If you're just getting
+started,
+[x/example/gotypes](https://github.com/golang/example/tree/master/gotypes) is
+a great introduction (and was the inspiration for this guide).
+
+# Summary of new language features and their APIs
+
+The introduction of of generic features appears as a large change to the
+language, but a high level introduces only a few new concepts. We can break
+down our discussion into the following three broad categories: generic types,
+constraint interfaces, and instantiation. In each category below, the relevant
+new APIs are listed (some constructors and getters/setters may be elided where
+they are trivial):
+
+**Generic types**. Types and functions may be _generic_, meaning their
+declaration may have a non-empty _type parameter list_, as in
+`type List[T any] ...` or `func f[T1, T2 any]() { ... }`. Type parameter lists
+define placeholder types (_type parameters_), scoped to the declaration, which
+may be substituted by any type satisfying their corresponding _constraint
+interface_ to _instantiate_ a new type or function.
+
+Generic types may have methods, which declare `receiver type parameters` via
+their receiver type expression: `func (r T[P1, ..., PN]) method(...) (...)
+{...}`.
+
+_New APIs_:
+ - The field `ast.TypeSpec.TypeParams` holds the type parameter list syntax for
+ type declarations.
+ - The field `ast.FuncType.TypeParams` holds the type parameter list syntax for
+ function declarations.
+ - The type `types.TypeParam` is a `types.Type` representing a type parameter.
+ On this type, the `Constraint` and `SetConstraint` methods allow
+ getting/setting the constraint, the `Index` method returns the numeric index
+ of the type parameter in the type parameter list that declares it, and the
+ `Obj` method returns the object in the scope a for the type parameter (a
+ `types.TypeName`). Generic type declarations have a new `*types.Scope` for
+ type parameter declarations.
+ - The type `types.TypeParamList` holds a list of type parameters.
+ - The method `types.Named.TypeParams` returns the type parameters for a type
+ declaration.
+ - The method `types.Named.SetTypeParams` sets type parameters on a defined
+ type.
+ - The function `types.NewSignatureType` creates a new (possibly generic)
+ signature type.
+ - The method `types.Signature.RecvTypeParams` returns the receiver type
+ parameters for a method.
+ - The method `types.Signature.TypeParams` returns the type parameters for
+ a function.
+
+**Constraint Interfaces**: type parameter constraints are interfaces, expressed
+by an interface type expression. Interfaces that are only used in constraint
+position are permitted new embedded elements composed of tilde expressions
+(`~T`) and unions (`A | B | ~C`). The new builtin interface type `comparable`
+is implemented by types for which `==` and `!=` are valid (note that interfaces
+must be statically comparable in this case, i.e., each type in the interface's
+type set must be comparable). As a special case, the `interface` keyword may be
+omitted from constraint expressions if it may be implied (in which case we say
+the interface is _implicit_).
+
+_New APIs_:
+ - The constant `token.TILDE` is used to represent tilde expressions as an
+ `ast.UnaryExpr`.
+ - Union expressions are represented as an `ast.BinaryExpr` using `|`. This
+ means that `ast.BinaryExpr` may now be both a type and value expression.
+ - The method `types.Interface.IsImplicit` reports whether the `interface`
+ keyword was elided from this interface.
+ - The method `types.Interface.MarkImplicit` marks an interface as being
+ implicit.
+ - The method `types.Interface.IsComparable` reports whether every type in an
+ interface's type set is comparable.
+ - The method `types.Interface.IsMethodSet` reports whether an interface is
+ defined entirely by its methods (has no _specific types_).
+ - The type `types.Union` is a type that represents an embedded union
+ expression in an interface. May only appear as an embedded element in
+ interfaces.
+ - The type `types.Term` represents a (possibly tilde) term of a union.
+
+**Instantiation**: generic types and functions may be _instantiated_ to create
+non-generic types and functions by providing _type arguments_ (`var x T[int]`).
+Function type arguments may be _inferred_ via function arguments, or via
+type parameter constraints.
+
+_New APIs_:
+ - The type `ast.IndexListExpr` holds index expressions with multiple indices,
+ as in instantiation expressions with multiple type arguments or in receivers
+ declaring multiple type parameters.
+ - The function `types.Instantiate` instantiates a generic type with type arguments.
+ - The type `types.Context` is an opaque instantiation context that may be
+ shared to reduce duplicate instances.
+ - The field `types.Config.Context` holds a shared `Context` to use for
+ instantiation while type-checking.
+ - The type `types.TypeList` holds a list of types.
+ - The type `types.ArgumentError` holds an error associated with a specific
+ type argument index. Used to represent instantiation errors.
+ - The field `types.Info.Instances` maps instantiated identifiers to information
+ about the resulting type instance.
+ - The type `types.Instance` holds information about a type or function
+ instance.
+ - The method `types.Named.TypeArgs` reports the type arguments used to
+ instantiate a named type.
+
+# Examples
+
+The following examples demonstrate the new APIs, and discuss their properties.
+All examples are runnable, contained in subdirectories of the directory holding
+this README.
+
+## Generic types: type parameters
+
+We say that a type is _generic_ if it has type parameters but no type
+arguments. This section explains how we can inspect generic types with the new
+`go/types` APIs.
+
+### Type parameter lists
+
+Suppose we want to understand the generic library below, which defines a generic
+`Pair`, a constraint interface `Constraint`, and a generic function `MakePair`.
+
+%include findtypeparams/main.go input -
+
+We can use the new `TypeParams` fields in `ast.TypeSpec` and `ast.FuncType` to
+access the type parameter list. From there, we can access type parameter types
+in at least three ways:
+ - by looking up type parameter definitions in `types.Info`
+ - by calling `TypeParams()` on `types.Named` or `types.Signature`
+ - by looking up type parameter objects in the declaration scope. Note that
+ there now may be a scope associated with an `ast.TypeSpec` node.
+
+%include findtypeparams/main.go print -
+
+This program produces the following output. Note that not every type spec has
+a scope.
+
+%include findtypeparams/main.go output -
+
+## Constraint Interfaces
+
+In order to allow operations on type parameters, Go 1.18 introduces the notion
+of [_type sets_](https://tip.golang.org/ref/spec#Interface_types), which is
+abstractly the set of types that implement an interface. This section discusses
+the new syntax for restrictions on interface type sets, and the APIs we can use
+to understand them.
+
+### New interface elements
+
+Consider the generic library below:
+
+%include interfaces/main.go input -
+
+In this library, we can see a few new features added in Go 1.18. The first is
+the new syntax in the `Numeric` type: unions of tilde-terms, specifying that
+the numeric type may only be satisfied by types whose underlying type is `int`
+or `float64`.
+
+The `go/ast` package parses this new syntax as a combination of unary and
+binary expressions, which we can see using the following program:
+
+%include interfaces/main.go printsyntax -
+
+Output:
+
+%include interfaces/main.go outputsyntax -
+
+Once type-checked, these embedded expressions are represented using the new
+`types.Union` type, which flattens the expression into a list of `*types.Term`.
+We can also investigate two new methods of interface:
+`types.Interface.IsComparable`, which reports whether the type set of an
+interface is comparable, and `types.Interface.IsMethodSet`, which reports
+whether an interface is expressable using methods alone.
+
+%include interfaces/main.go printtypes -
+
+Output:
+
+%include interfaces/main.go outputtypes -
+
+The `Findable` type demonstrates another new feature of Go 1.18: the comparable
+built-in. Comparable is a special interface type, not expressable using
+ordinary Go syntax, whose type-set consists of all comparable types.
+
+### Implicit interfaces
+
+For interfaces that do not have methods, we can inline them in constraints and
+elide the `interface` keyword. In the example above, we could have done this
+for the `Square` function:
+
+%include implicit/main.go input -
+
+In such cases, the `types.Interface.IsImplicit` method reports whether the
+interface type was implicit. This does not affect the behavior of the
+interface, but is captured for more accurate type strings:
+
+%include implicit/main.go show -
+
+Output:
+
+%include implicit/main.go output -
+
+The `types.Interface.MarkImplicit` method is used to mark interfaces as
+implicit by the importer.
+
+### Type sets
+
+The examples above demonstrate the new APIs for _accessing_ information about
+the new interface elements, but how do we understand
+[_type sets_](https://tip.golang.org/ref/spec#Interface_types), the new
+abstraction that these elements help define? Type sets may be arbitrarily
+complex, as in the following example:
+
+%include typesets/main.go input -
+
+Here, the type set of `D` simplifies to `~string|int`, but the current
+`go/types` APIs do not expose this information. This will likely be added to
+`go/types` in future versions of Go, but in the meantime we can use the
+`typeparams.NormalTerms` helper:
+
+%include typesets/main.go print -
+
+which outputs:
+
+%include typesets/main.go output -
+
+See the documentation for `typeparams.NormalTerms` for more information on how
+this calculation proceeds.
+
+## Instantiation
+
+We say that a type is _instantiated_ if it is created from a generic type by
+substituting type arguments for type parameters. Instantiation can occur via
+explicitly provided type arguments, as in the expression `T[A_1, ..., A_n]`, or
+implicitly, through type inference.. This section describes how to find and
+understand instantiated types.
+
+### Finding instantiated types
+
+Certain applications may find it useful to locate all instantiated types in
+a package. For this purpose, `go/types` provides a new `types.Info.Instances`
+field that maps instantiated identifiers to information about their instance.
+
+For example, consider the following code:
+
+%include instantiation/main.go input -
+
+We can find instances by type-checking with the `types.Info.Instances` map
+initialized:
+
+%include instantiation/main.go check -
+
+Output:
+
+%include instantiation/main.go checkoutput -
+
+The `types.Instance` type provides information about the (possibly inferred)
+type arguments that were used to instantiate the generic type, and the
+resulting type. Notably, it does not include the _generic_ type that was
+instantiated, because this type can be found using `types.Info.Uses[id].Type()`
+(where `id` is the identifier node being instantiated).
+
+Note that the receiver type of method `Left` also appears in the `Instances`
+map. This may be counterintuitive -- more on this below.
+
+### Creating new instantiated types
+
+`go/types` also provides an API for creating type instances:
+`types.Instantiate`. This function accepts a generic type and type arguments,
+and returns an instantiated type (or an error). The resulting instance may be
+a newly constructed type, or a previously created instance with the same type
+identity. To facilitate the reuse of frequently used instances,
+`types.Instantiate` accepts a `types.Context` as its first argument, which
+records instances.
+
+If the final `validate` argument to `types.Instantiate` is set, the provided
+type arguments will be verified against their corresponding type parameter
+constraint; i.e., `types.Instantiate` will check that each type arguments
+implements the corresponding type parameter constraint. If a type arguments
+does not implement the respective constraint, the resulting error will wrap
+a new `ArgumentError` type indicating which type argument index was bad.
+
+%include instantiation/main.go instantiate -
+
+Output:
+
+%include instantiation/main.go instantiateoutput -
+
+### Using a shared context while type checking
+
+To share a common `types.Context` argument with a type-checking pass, set the
+new `types.Config.Context` field.
+
+## Generic types continued: method sets and predicates
+
+Generic types are fundamentally different from ordinary types, in that they may
+not be used without instantiation. In some senses they are not really types:
+the go spec defines [types](https://tip.golang.org/ref/spec#Types) as "a set of
+values, together with operations and methods", but uninstantiated generic types
+do not define a set of values. Rather, they define a set of _types_. In that
+sense, they are a "meta type", or a "type template" (disclaimer: I am using
+these terms imprecisely).
+
+However, for the purposes of `go/types` it is convenient to treat generic types
+as a `types.Type`. This section explains how generic types behave in existing
+`go/types` APIs.
+
+### Method Sets
+
+Methods on uninstantiated generic types are different from methods on an
+ordinary type. Consider that for an ordinary type `T`, the receiver base type
+of each method in its method set is `T`. However, this can't be the case for
+a generic type: generic types cannot be used without instantation, and neither
+can the type of the receiver variable. Instead, the receiver base type is an
+_instantiated_ type, instantiated with the method's receiver type parameters.
+
+This has some surprising consequences, which we observed in the section on
+instantiation above: for a generic type `G`, each of its methods will define
+a unique instantiation of `G`, as each method has distinct receiver type
+parameters.
+
+To see this, consider the following example:
+
+%include genericmethods/main.go input -
+
+Let's inspect the method sets of the types in this library:
+
+%include genericmethods/main.go printmethods -
+
+Output:
+
+%include genericmethods/main.go printoutput -
+
+In this example, we can see that all of `Pair`, `Pair[int, int]`, and
+`Pair[L, _]` have distinct method sets, though the method set of `Pair` and
+`Pair[L, _]` intersect in the `Left` method.
+
+Only the objects in `Pair`'s method set are recorded in `types.Info.Defs`. To
+get back to this "canonical" method object, the `typeparams` package provides
+the `OriginMethod` helper:
+
+%include genericmethods/main.go compareorigins -
+
+Output:
+
+%include genericmethods/main.go compareoutput -
+
+### Predicates
+
+Predicates on generic types are not defined by the spec. As a consequence,
+using e.g. `types.AssignableTo` with operands of generic types leads to an
+undefined result.
+
+The behavior of predicates on generic `*types.Named` types may generally be
+derived from the fact that type parameters bound to different names are
+different types. This means that most predicates involving generic types will
+return `false`.
+
+`*types.Signature` types are treated differently. Two signatures are considered
+identical if they are identical after substituting one's set of type parameters
+for the other's, including having identical type parameter constraints. This is
+analogous to the treatment of ordinary value parameters, whose names do not
+affect type identity.
+
+Consider the following code:
+
+%include predicates/main.go ordinary -
+
+Output:
+
+%include predicates/main.go ordinaryoutput -
+
+In this example, we see that despite their similarity the generic `Pair` type
+is not assignable to the generic `LeftRighter` type. We also see the rules for
+signature identity in practice.
+
+This begs the question: how does one ask questions about the relationship
+between generic types? In order to phrase such questions we need more
+information: how does one relate the type parameters of `Pair` to the type
+parameters of `LeftRighter`? Does it suffice for the predicate to hold for one
+element of the type sets, or must it hold for all elements of the type sets?
+
+We can use instantiation to answer some of these questions. In particular, by
+instantiating both `Pair` and `LeftRighter` with the type parameters of `Pair`,
+we can determine if, for all type arguments `[X, Y]` that are valid for `Pair`,
+`[X, Y]` are also valid type arguments of `LeftRighter`, and `Pair[X, Y]` is
+assignable to `LeftRighter[X, Y]`. The `typeparams.GenericAssignableTo`
+function implements exactly this predicate:
+
+%include predicates/main.go generic -
+
+Output:
+
+%include predicates/main.go genericoutput -
+
+# Updating tools while building at older Go versions
+
+In the examples above, we can see how a lot of the new APIs integrate with
+existing usage of `go/ast` or `go/types`. However, most tools still need to
+build at older Go versions, and handling the new language constructs in-line
+will break builds at older Go versions.
+
+For this purpose, the `x/exp/typeparams` package provides functions and types
+that proxy the new APIs (with stub implementations at older Go versions).
+
+# Further help
+
+If you're working on updating a tool to support generics, and need help, please
+feel free to reach out for help in any of the following ways:
+ - By mailing the [golang-tools](https://groups.google.com/g/golang-tools) mailing list.
+ - Directly to me via email (`rfindley@google.com`).
+ - For bugs, you can [file an issue](https://github.com/golang/go/issues/new/choose).
diff --git a/typeparams/example/genericmethods/main.go b/typeparams/example/genericmethods/main.go
new file mode 100644
index 0000000..6aa5e7a
--- /dev/null
+++ b/typeparams/example/genericmethods/main.go
@@ -0,0 +1,122 @@
+// 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.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+var IntPair Pair[int, int]
+//!-input
+`
+
+// !+printmethods
+func PrintMethods(pkg *types.Package) {
+ // Look up *Named types in the package scope.
+ lookup := func(name string) *types.Named {
+ return pkg.Scope().Lookup(name).Type().(*types.Named)
+ }
+
+ Pair := lookup("Pair")
+ IntPair := lookup("IntPair")
+ PrintMethodSet("Pair", Pair)
+ PrintMethodSet("Pair[int, int]", IntPair)
+ LeftObj, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftRecvType := LeftObj.Type().(*types.Signature).Recv().Type()
+ PrintMethodSet("Pair[L, _]", LeftRecvType)
+}
+
+func PrintMethodSet(name string, typ types.Type) {
+ fmt.Println(name + ":")
+ methodSet := types.NewMethodSet(typ)
+ for i := 0; i < methodSet.Len(); i++ {
+ method := methodSet.At(i).Obj()
+ fmt.Println(method)
+ }
+ fmt.Println()
+}
+
+//!-printmethods
+
+/*
+//!+printoutput
+Pair:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[_, R]).Right() R
+
+Pair[int, int]:
+func (p.Pair[int, int]).Left() int
+func (p.Pair[int, int]).Right() int
+
+Pair[L, _]:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[L, _]).Right() _
+
+//!-printoutput
+*/
+
+// !+compareorigins
+func CompareOrigins(pkg *types.Package) {
+ Pair := pkg.Scope().Lookup("Pair").Type().(*types.Named)
+ IntPair := pkg.Scope().Lookup("IntPair").Type().(*types.Named)
+ Left, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftInt, _, _ := types.LookupFieldOrMethod(IntPair, false, pkg, "Left")
+
+ fmt.Println("Pair.Left == Pair[int, int].Left:", Left == LeftInt)
+ origin := typeparams.OriginMethod(LeftInt.(*types.Func))
+ fmt.Println("Pair.Left == OriginMethod(Pair[int, int].Left):", Left == origin)
+}
+
+//!-compareorigins
+
+/*
+//!+compareoutput
+Pair.Left == Pair[int, int].Left: false
+Pair.Left == OriginMethod(Pair[int, int].Left): true
+//!-compareoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== PrintMethods ===")
+ PrintMethods(pkg)
+ fmt.Println("=== CompareOrigins ===")
+ CompareOrigins(pkg)
+}
diff --git a/typeparams/example/implicit/main.go b/typeparams/example/implicit/main.go
new file mode 100644
index 0000000..996ad2c
--- /dev/null
+++ b/typeparams/example/implicit/main.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+func Square[N ~int|~float64](n N) N {
+ return n*n
+}
+//!-input
+`
+
+// !+show
+func ShowImplicit(pkg *types.Package) {
+ Square := pkg.Scope().Lookup("Square").Type().(*types.Signature)
+ N := Square.TypeParams().At(0)
+ constraint := N.Constraint().(*types.Interface)
+ fmt.Println(constraint)
+ fmt.Println("IsImplicit:", constraint.IsImplicit())
+}
+
+//!-show
+
+/*
+//!+output
+~int|~float64
+IsImplicit: true
+//!-output
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("typesets", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ ShowImplicit(pkg)
+}
diff --git a/typeparams/example/instantiation/main.go b/typeparams/example/instantiation/main.go
new file mode 100644
index 0000000..ed53667
--- /dev/null
+++ b/typeparams/example/instantiation/main.go
@@ -0,0 +1,158 @@
+// 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.
+
+//go:build go1.18
+
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func Equal[L, R comparable](x, y Pair[L, R]) bool {
+ return x.left == y.left && x.right == y.right
+}
+
+var X Pair[int, string]
+var Y Pair[string, int]
+
+var E = Equal[int, string]
+//!-input
+`
+
+// !+check
+func CheckInstances(fset *token.FileSet, file *ast.File) (*types.Package, error) {
+ conf := types.Config{}
+ info := &types.Info{
+ Instances: make(map[*ast.Ident]types.Instance),
+ }
+ pkg, err := conf.Check("p", fset, []*ast.File{file}, info)
+ for id, inst := range info.Instances {
+ posn := fset.Position(id.Pos())
+ fmt.Printf("%s: %s instantiated with %s: %s\n", posn, id.Name, FormatTypeList(inst.TypeArgs), inst.Type)
+ }
+ return pkg, err
+}
+
+//!-check
+
+/*
+//!+checkoutput
+hello.go:21:9: Equal instantiated with [int, string]: func(x p.Pair[int, string], y p.Pair[int, string]) bool
+hello.go:10:9: Pair instantiated with [L, _]: p.Pair[L, _]
+hello.go:14:34: Pair instantiated with [L, R]: p.Pair[L, R]
+hello.go:18:7: Pair instantiated with [int, string]: p.Pair[int, string]
+hello.go:19:7: Pair instantiated with [string, int]: p.Pair[string, int]
+
+//!-checkoutput
+*/
+
+func FormatTypeList(list *types.TypeList) string {
+ var buf bytes.Buffer
+ buf.WriteRune('[')
+ for i := 0; i < list.Len(); i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(list.At(i).String())
+ }
+ buf.WriteRune(']')
+ return buf.String()
+}
+
+// !+instantiate
+func Instantiate(pkg *types.Package) error {
+ Pair := pkg.Scope().Lookup("Pair").Type()
+ X := pkg.Scope().Lookup("X").Type()
+ Y := pkg.Scope().Lookup("Y").Type()
+
+ // X and Y have different types, because their type arguments are different.
+ Compare("X", "Y", X, Y)
+
+ // Create a shared context for the subsequent instantiations.
+ ctxt := types.NewContext()
+
+ // Instantiating with [int, string] yields an instance that is identical (but
+ // not equal) to X.
+ Int, String := types.Typ[types.Int], types.Typ[types.String]
+ inst1, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("X", "inst1", X, inst1)
+
+ // Instantiating again returns the same exact instance, because of the shared
+ // Context.
+ inst2, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("inst1", "inst2", inst1, inst2)
+
+ // Instantiating with 'any' is an error, because any is not comparable.
+ Any := types.Universe.Lookup("any").Type()
+ _, err := types.Instantiate(ctxt, Pair, []types.Type{Int, Any}, true)
+ var argErr *types.ArgumentError
+ if errors.As(err, &argErr) {
+ fmt.Printf("Argument %d: %v\n", argErr.Index, argErr.Err)
+ }
+
+ return nil
+}
+
+func Compare(leftName, rightName string, left, right types.Type) {
+ fmt.Printf("Identical(%s, %s) : %t\n", leftName, rightName, types.Identical(left, right))
+ fmt.Printf("%s == %s : %t\n\n", leftName, rightName, left == right)
+}
+
+//!-instantiate
+
+/*
+//!+instantiateoutput
+Identical(p.Pair[int, string], p.Pair[string, int]) : false
+p.Pair[int, string] == p.Pair[string, int] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : false
+
+Identical(p.Pair[string, int], p.Pair[int, string]) : false
+p.Pair[string, int] == p.Pair[int, string] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : true
+
+Argument 1: any does not implement comparable
+//!-instantiateoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== CheckInstances ===")
+ pkg, err := CheckInstances(fset, f)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== Instantiate ===")
+ if err := Instantiate(pkg); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/interfaces/main.go b/typeparams/example/interfaces/main.go
new file mode 100644
index 0000000..6a0d627
--- /dev/null
+++ b/typeparams/example/interfaces/main.go
@@ -0,0 +1,138 @@
+// 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.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+type Numeric interface{
+ ~int | ~float64 // etc...
+}
+
+func Square[N Numeric](n N) N {
+ return n*n
+}
+
+type Findable interface {
+ comparable
+}
+
+func Find[T Findable](s []T, v T) int {
+ for i, v2 := range s {
+ if v2 == v {
+ return i
+ }
+ }
+ return -1
+}
+//!-input
+`
+
+// !+printsyntax
+func PrintNumericSyntax(fset *token.FileSet, file *ast.File) {
+ // node is the AST node corresponding to the declaration for "Numeric."
+ node := file.Scope.Lookup("Numeric").Decl.(*ast.TypeSpec)
+ // Find the embedded syntax node.
+ embedded := node.Type.(*ast.InterfaceType).Methods.List[0].Type
+ // Use go/ast's built-in Print function to inspect the parsed syntax.
+ ast.Print(fset, embedded)
+}
+
+//!-printsyntax
+
+/*
+//!+outputsyntax
+ 0 *ast.BinaryExpr {
+ 1 . X: *ast.UnaryExpr {
+ 2 . . OpPos: p.go:6:2
+ 3 . . Op: ~
+ 4 . . X: *ast.Ident {
+ 5 . . . NamePos: p.go:6:3
+ 6 . . . Name: "int"
+ 7 . . }
+ 8 . }
+ 9 . OpPos: p.go:6:7
+ 10 . Op: |
+ 11 . Y: *ast.UnaryExpr {
+ 12 . . OpPos: p.go:6:9
+ 13 . . Op: ~
+ 14 . . X: *ast.Ident {
+ 15 . . . NamePos: p.go:6:10
+ 16 . . . Name: "float64"
+ 17 . . }
+ 18 . }
+ 19 }
+//!-outputsyntax
+*/
+
+// !+printtypes
+func PrintInterfaceTypes(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{}
+ pkg, err := conf.Check("hello", fset, []*ast.File{file}, nil)
+ if err != nil {
+ return err
+ }
+
+ PrintIface(pkg, "Numeric")
+ PrintIface(pkg, "Findable")
+
+ return nil
+}
+
+func PrintIface(pkg *types.Package, name string) {
+ obj := pkg.Scope().Lookup(name)
+ fmt.Println(obj)
+ iface := obj.Type().Underlying().(*types.Interface)
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ for i := 0; i < iface.NumMethods(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ fmt.Printf("\tIsComparable(): %t\n", iface.IsComparable())
+ fmt.Printf("\tIsMethodSet(): %t\n", iface.IsMethodSet())
+}
+
+//!-printtypes
+
+/*
+//!+outputtypes
+type hello.Numeric interface{~int|~float64}
+ embeded: ~int|~float64
+ IsComparable(): true
+ IsMethodSet(): false
+type hello.Findable interface{comparable}
+ embeded: comparable
+ IsComparable(): true
+ IsMethodSet(): false
+//!-outputtypes
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== PrintNumericSyntax ==")
+ PrintNumericSyntax(fset, f)
+ fmt.Println("=== PrintInterfaceTypes ==")
+ if err := PrintInterfaceTypes(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/methoddecls/main.go b/typeparams/example/methoddecls/main.go
new file mode 100644
index 0000000..52ae8be
--- /dev/null
+++ b/typeparams/example/methoddecls/main.go
@@ -0,0 +1,137 @@
+// 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.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const methods = `
+//!+input
+package methods
+
+type List[E any] []E
+
+func (l List[_]) Len() int {
+ return len(l)
+}
+
+func (l *List[E]) Append(v E) {
+ *l = append(*l, v)
+}
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+func (p Pair[L, R]) Equal(other Pair[L, R]) bool {
+ return p.Left() == other.Left() && p.Right() == other.Right()
+}
+//!-input
+`
+
+// !+describe
+func Describe(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ pkg, err := conf.Check("pair", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+ for _, name := range pkg.Scope().Names() {
+ obj := pkg.Scope().Lookup(name)
+ typ := obj.Type().(*types.Named)
+
+ fmt.Printf("type %s has %d methods\n", name, typ.NumMethods())
+ for i := 0; i < typ.NumMethods(); i++ {
+ m := typ.Method(i)
+ sig := m.Type().(*types.Signature)
+ recv := sig.Recv().Type()
+ fmt.Printf(" %s has %d receiver type parameters\n", m.Name(), sig.RecvTypeParams().Len())
+ fmt.Printf(" ...and receiver type %+v\n", recv)
+ }
+ }
+ // }
+
+ // func foo() {}
+ for _, decl := range file.Decls {
+ fdecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ fmt.Printf("Declaration of %q has receiver node type %T\n", fdecl.Name, fdecl.Recv.List[0].Type)
+ declObj := info.Defs[fdecl.Name]
+ recvIdent := receiverTypeName(fdecl.Recv.List[0].Type)
+ typ := pkg.Scope().Lookup(recvIdent.Name).Type().(*types.Named)
+ // ptr := types.NewPointer(typ)
+ name := fdecl.Name.Name
+ methodObj, _, _ := types.LookupFieldOrMethod(typ, false, pkg, name)
+ if declObj == methodObj {
+ fmt.Printf(" info.Uses[%s] == types.LookupFieldOrMethod(%s, ..., %q)\n", fdecl.Name, typ, name)
+ }
+ }
+ return nil
+}
+
+func receiverTypeName(e ast.Expr) *ast.Ident {
+ if s, ok := e.(*ast.StarExpr); ok {
+ e = s.X
+ }
+ switch e := e.(type) {
+ case *ast.IndexExpr:
+ return e.X.(*ast.Ident)
+ case *ast.IndexListExpr:
+ return e.X.(*ast.Ident)
+ }
+ panic("unexpected receiver node type")
+}
+
+//!-describe
+
+/*
+//!+output
+> go run golang.org/x/tools/internal/typeparams/example/methoddecls
+Pair has 2 methods
+ Left has 2 receiver type parameters
+ ...and receiver type pair.Pair[L, _]
+ Right has 2 receiver type parameters
+ ...and receiver type pair.Pair[_, R]
+Declaration of "Left" has receiver node type *ast.IndexListExpr
+ info.Uses[Left] == types.LookupFieldOrMethod(pair.Pair[L, R any], ..., "Left")
+Declaration of "Right" has receiver node type *ast.IndexListExpr
+ info.Uses[Right] == types.LookupFieldOrMethod(pair.Pair[L, R any], ..., "Right")
+//!-output
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "methods.go", methods, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := Describe(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/predicates/main.go b/typeparams/example/predicates/main.go
new file mode 100644
index 0000000..5237c06
--- /dev/null
+++ b/typeparams/example/predicates/main.go
@@ -0,0 +1,114 @@
+// 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 main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+// M does not use type parameters, and therefore implements the Mer interface.
+func (p Pair[_, _]) M() int { return 0 }
+
+type LeftRighter[L, R comparable] interface {
+ Left() L
+ Right() R
+}
+
+type Mer interface {
+ M() int
+}
+
+// F and G have identical signatures "modulo renaming", H does not.
+func F[P any](P) int { return 0 }
+func G[Q any](Q) int { return 1 }
+func H[R ~int](R) int { return 2 }
+//!-input
+`
+
+// !+ordinary
+func OrdinaryPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ Mer = pkg.Scope().Lookup("Mer").Type()
+ F = pkg.Scope().Lookup("F").Type()
+ G = pkg.Scope().Lookup("G").Type()
+ H = pkg.Scope().Lookup("H").Type()
+ )
+
+ fmt.Println("AssignableTo(Pair, LeftRighter)", types.AssignableTo(Pair, LeftRighter))
+ fmt.Println("AssignableTo(Pair, Mer): ", types.AssignableTo(Pair, Mer))
+ fmt.Println("Identical(F, G)", types.Identical(F, G))
+ fmt.Println("Identical(F, H)", types.Identical(F, H))
+}
+
+//!-ordinary
+
+/*
+//!+ordinaryoutput
+AssignableTo(Pair, LeftRighter) false
+AssignableTo(Pair, Mer): true
+Identical(F, G) true
+Identical(F, H) false
+//!-ordinaryoutput
+*/
+
+// !+generic
+func GenericPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ )
+ fmt.Println("GenericAssignableTo(Pair, LeftRighter)", typeparams.GenericAssignableTo(nil, Pair, LeftRighter))
+}
+
+//!-generic
+
+/*
+//!+genericoutput
+GenericAssignableTo(Pair, LeftRighter) true
+//!-genericoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== ordinary ===")
+ OrdinaryPredicates(pkg)
+ fmt.Println("=== generic ===")
+ GenericPredicates(pkg)
+}
diff --git a/typeparams/example/typesets/main.go b/typeparams/example/typesets/main.go
new file mode 100644
index 0000000..05da8be
--- /dev/null
+++ b/typeparams/example/typesets/main.go
@@ -0,0 +1,71 @@
+// 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 main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package complex
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type D interface{ A|B; C }
+//!-input
+`
+
+// !+print
+func PrintNormalTerms(pkg *types.Package) error {
+ D := pkg.Scope().Lookup("D").Type()
+ terms, err := typeparams.NormalTerms(D)
+ if err != nil {
+ return err
+ }
+ for i, term := range terms {
+ if i > 0 {
+ fmt.Print("|")
+ }
+ fmt.Print(term)
+ }
+ fmt.Println()
+ return nil
+}
+
+//!-print
+
+/*
+//!+output
+~string|int
+//!-output
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("typesets", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := PrintNormalTerms(pkg); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/go.mod b/typeparams/go.mod
new file mode 100644
index 0000000..b1224b1
--- /dev/null
+++ b/typeparams/go.mod
@@ -0,0 +1,3 @@
+module golang.org/x/exp/typeparams
+
+go 1.18
diff --git a/typeparams/normalize.go b/typeparams/normalize.go
new file mode 100644
index 0000000..6cf71f0
--- /dev/null
+++ b/typeparams/normalize.go
@@ -0,0 +1,200 @@
+// Copyright 2021 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 typeparams
+
+import (
+ "errors"
+ "fmt"
+ "go/types"
+ "os"
+ "strings"
+)
+
+const debug = false
+
+// ErrEmptyTypeSet is returned if a type set computation results in a type set
+// with no types.
+var ErrEmptyTypeSet = errors.New("empty type set")
+
+// NormalTerms returns a slice of terms representing the normalized structural
+// type restrictions of a type, if any.
+//
+// For all types whose underlying type is not *types.TypeParam,
+// *types.Interface, or *types.Union, this is just a single term with Tilde()
+// == false and Type() == typ. For types whose underlying type is
+// *types.TypeParam, *types.Interface, and *types.Union, see below.
+//
+// Structural type restrictions of a type parameter are created via
+// non-interface types embedded in its constraint interface (directly, or via a
+// chain of interface embeddings). For example, in the declaration type T[P
+// interface{~int; m()}] int is the structural restriction of the type
+// parameter P is ~int.
+//
+// With interface embedding and unions, the specification of structural type
+// restrictions may be arbitrarily complex. For example, consider the
+// following:
+//
+// type A interface{ ~string|~[]byte }
+//
+// type B interface{ int|string }
+//
+// type C interface { ~string|~int }
+//
+// type T[P interface{ A|B; C }] int
+//
+// In this example, the structural type restriction of P is ~string|int: A|B
+// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
+// which when intersected with C (~string|~int) yields ~string|int.
+//
+// NormalTerms computes these expansions and reductions, producing a
+// "normalized" form of the embeddings. A structural restriction is normalized
+// if it is a single union containing no interface terms, and is minimal in the
+// sense that removing any term changes the set of types satisfying the
+// constraint. It is left as a proof for the reader that, modulo sorting, there
+// is exactly one such normalized form.
+//
+// Because the minimal representation always takes this form, NormalTerms
+// returns a slice of tilde terms corresponding to the terms of the union in
+// the normalized structural restriction. An error is returned if the type is
+// invalid, exceeds complexity bounds, or has an empty type set. In the latter
+// case, NormalTerms returns ErrEmptyTypeSet.
+//
+// NormalTerms makes no guarantees about the order of terms, except that it
+// is deterministic.
+func NormalTerms(typ types.Type) ([]*Term, error) {
+ if tparam, ok := typ.(*TypeParam); ok {
+ constraint := tparam.Constraint()
+ if constraint == nil {
+ return nil, fmt.Errorf("%s has nil constraint", tparam)
+ }
+ iface, _ := constraint.Underlying().(*types.Interface)
+ if iface == nil {
+ return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
+ }
+ typ = iface
+ }
+ tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
+ if err != nil {
+ return nil, err
+ }
+ if tset.terms.isEmpty() {
+ return nil, ErrEmptyTypeSet
+ }
+ if tset.terms.isAll() {
+ return nil, nil
+ }
+ var terms []*Term
+ for _, term := range tset.terms {
+ terms = append(terms, NewTerm(term.tilde, term.typ))
+ }
+ return terms, nil
+}
+
+// A termSet holds the normalized set of terms for a given type.
+//
+// The name termSet is intentionally distinct from 'type set': a type set is
+// all types that implement a type (and includes method restrictions), whereas
+// a term set just represents the structural restrictions on a type.
+type termSet struct {
+ complete bool
+ terms termlist
+}
+
+func indentf(depth int, format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
+}
+
+func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) {
+ if t == nil {
+ panic("nil type")
+ }
+
+ if debug {
+ indentf(depth, "%s", t.String())
+ defer func() {
+ if err != nil {
+ indentf(depth, "=> %s", err)
+ } else {
+ indentf(depth, "=> %s", res.terms.String())
+ }
+ }()
+ }
+
+ const maxTermCount = 100
+ if tset, ok := seen[t]; ok {
+ if !tset.complete {
+ return nil, fmt.Errorf("cycle detected in the declaration of %s", t)
+ }
+ return tset, nil
+ }
+
+ // Mark the current type as seen to avoid infinite recursion.
+ tset := new(termSet)
+ defer func() {
+ tset.complete = true
+ }()
+ seen[t] = tset
+
+ switch u := t.Underlying().(type) {
+ case *types.Interface:
+ // The term set of an interface is the intersection of the term sets of its
+ // embedded types.
+ tset.terms = allTermlist
+ for i := 0; i < u.NumEmbeddeds(); i++ {
+ embedded := u.EmbeddedType(i)
+ if _, ok := embedded.Underlying().(*TypeParam); ok {
+ return nil, fmt.Errorf("invalid embedded type %T", embedded)
+ }
+ tset2, err := computeTermSetInternal(embedded, seen, depth+1)
+ if err != nil {
+ return nil, err
+ }
+ tset.terms = tset.terms.intersect(tset2.terms)
+ }
+ case *Union:
+ // The term set of a union is the union of term sets of its terms.
+ tset.terms = nil
+ for i := 0; i < u.Len(); i++ {
+ t := u.Term(i)
+ var terms termlist
+ switch t.Type().Underlying().(type) {
+ case *types.Interface:
+ tset2, err := computeTermSetInternal(t.Type(), seen, depth+1)
+ if err != nil {
+ return nil, err
+ }
+ terms = tset2.terms
+ case *TypeParam, *Union:
+ // A stand-alone type parameter or union is not permitted as union
+ // term.
+ return nil, fmt.Errorf("invalid union term %T", t)
+ default:
+ if t.Type() == types.Typ[types.Invalid] {
+ continue
+ }
+ terms = termlist{{t.Tilde(), t.Type()}}
+ }
+ tset.terms = tset.terms.union(terms)
+ if len(tset.terms) > maxTermCount {
+ return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
+ }
+ }
+ case *TypeParam:
+ panic("unreachable")
+ default:
+ // For all other types, the term set is just a single non-tilde term
+ // holding the type itself.
+ if u != types.Typ[types.Invalid] {
+ tset.terms = termlist{{false, t}}
+ }
+ }
+ return tset, nil
+}
+
+// under is a facade for the go/types internal function of the same name. It is
+// used by typeterm.go.
+func under(t types.Type) types.Type {
+ return t.Underlying()
+}
diff --git a/typeparams/normalize_test.go b/typeparams/normalize_test.go
new file mode 100644
index 0000000..98f7ec4
--- /dev/null
+++ b/typeparams/normalize_test.go
@@ -0,0 +1,104 @@
+// Copyright 2021 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 typeparams_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "regexp"
+ "strings"
+ "testing"
+
+ . "golang.org/x/exp/typeparams"
+)
+
+func TestNormalTerms(t *testing.T) {
+ if !Enabled() {
+ t.Skip("typeparams are not enabled")
+ }
+
+ // In the following tests, src must define a type T with (at least) one type
+ // parameter. We will compute the normal terms of the first type parameter.
+ tests := []struct {
+ src string
+ want string
+ wantError string
+ }{
+ {"package emptyinterface0; type T[P interface{}] int", "all", ""},
+ {"package emptyinterface1; type T[P interface{ int | interface{} }] int", "all", ""},
+ {"package singleton; type T[P interface{ int }] int", "int", ""},
+ {"package under; type T[P interface{~int}] int", "~int", ""},
+ {"package superset; type T[P interface{ ~int | int }] int", "~int", ""},
+ {"package overlap; type T[P interface{ ~int; int }] int", "int", ""},
+ {"package emptyintersection; type T[P interface{ ~int; string }] int", "", "empty type set"},
+
+ {"package embedded0; type T[P interface{ I }] int; type I interface { int }", "int", ""},
+ {"package embedded1; type T[P interface{ I | string }] int; type I interface{ int | ~string }", "int ?\\| ?~string", ""},
+ {"package embedded2; type T[P interface{ I; string }] int; type I interface{ int | ~string }", "string", ""},
+
+ {"package named; type T[P C] int; type C interface{ ~int|int }", "~int", ""},
+ {`// package example is taken from the docstring for StructuralTerms
+package example
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type T[P interface{ A|B; C }] int
+`, "~string ?\\| ?int", ""},
+ }
+
+ for _, test := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", test.src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Run(f.Name.Name, func(t *testing.T) {
+ conf := types.Config{
+ Error: func(error) {}, // keep going on errors
+ }
+ pkg, err := conf.Check("", fset, []*ast.File{f}, nil)
+ if err != nil {
+ t.Logf("types.Config.Check: %v", err)
+ // keep going on type checker errors: we want to assert on behavior of
+ // invalid code as well.
+ }
+ obj := pkg.Scope().Lookup("T")
+ if obj == nil {
+ t.Fatal("type T not found")
+ }
+ T := ForNamed(obj.Type().(*types.Named)).At(0)
+ terms, err := NormalTerms(T)
+ if test.wantError != "" {
+ if err == nil {
+ t.Fatalf("StructuralTerms(%s): nil error, want %q", T, test.wantError)
+ }
+ if !strings.Contains(err.Error(), test.wantError) {
+ t.Errorf("StructuralTerms(%s): err = %q, want %q", T, err, test.wantError)
+ }
+ return
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got string
+ if len(terms) == 0 {
+ got = "all"
+ } else {
+ qf := types.RelativeTo(pkg)
+ got = types.TypeString(NewUnion(terms), qf)
+ }
+ want := regexp.MustCompile(test.want)
+ if !want.MatchString(got) {
+ t.Errorf("NormalTerms(%s) = %q, want matching %q", T, got, test.want)
+ }
+ })
+ }
+}
diff --git a/typeparams/termlist.go b/typeparams/termlist.go
new file mode 100644
index 0000000..6f88bfa
--- /dev/null
+++ b/typeparams/termlist.go
@@ -0,0 +1,172 @@
+// Copyright 2021 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.
+
+// Code generated by copytermlist.go DO NOT EDIT.
+
+package typeparams
+
+import (
+ "bytes"
+ "go/types"
+)
+
+// A termlist represents the type set represented by the union
+// t1 βˆͺ y2 βˆͺ ... tn of the type sets of the terms t1 to tn.
+// A termlist is in normal form if all terms are disjoint.
+// termlist operations don't require the operands to be in
+// normal form.
+type termlist []*term
+
+// allTermlist represents the set of all types.
+// It is in normal form.
+var allTermlist = termlist{new(term)}
+
+// String prints the termlist exactly (without normalization).
+func (xl termlist) String() string {
+ if len(xl) == 0 {
+ return "βˆ…"
+ }
+ var buf bytes.Buffer
+ for i, x := range xl {
+ if i > 0 {
+ buf.WriteString(" βˆͺ ")
+ }
+ buf.WriteString(x.String())
+ }
+ return buf.String()
+}
+
+// isEmpty reports whether the termlist xl represents the empty set of types.
+func (xl termlist) isEmpty() bool {
+ // If there's a non-nil term, the entire list is not empty.
+ // If the termlist is in normal form, this requires at most
+ // one iteration.
+ for _, x := range xl {
+ if x != nil {
+ return false
+ }
+ }
+ return true
+}
+
+// isAll reports whether the termlist xl represents the set of all types.
+func (xl termlist) isAll() bool {
+ // If there's a 𝓀 term, the entire list is 𝓀.
+ // If the termlist is in normal form, this requires at most
+ // one iteration.
+ for _, x := range xl {
+ if x != nil && x.typ == nil {
+ return true
+ }
+ }
+ return false
+}
+
+// norm returns the normal form of xl.
+func (xl termlist) norm() termlist {
+ // Quadratic algorithm, but good enough for now.
+ // TODO(gri) fix asymptotic performance
+ used := make([]bool, len(xl))
+ var rl termlist
+ for i, xi := range xl {
+ if xi == nil || used[i] {
+ continue
+ }
+ for j := i + 1; j < len(xl); j++ {
+ xj := xl[j]
+ if xj == nil || used[j] {
+ continue
+ }
+ if u1, u2 := xi.union(xj); u2 == nil {
+ // If we encounter a 𝓀 term, the entire list is 𝓀.
+ // Exit early.
+ // (Note that this is not just an optimization;
+ // if we continue, we may end up with a 𝓀 term
+ // and other terms and the result would not be
+ // in normal form.)
+ if u1.typ == nil {
+ return allTermlist
+ }
+ xi = u1
+ used[j] = true // xj is now unioned into xi - ignore it in future iterations
+ }
+ }
+ rl = append(rl, xi)
+ }
+ return rl
+}
+
+// If the type set represented by xl is specified by a single (non-𝓀) term,
+// singleType returns that type. Otherwise it returns nil.
+func (xl termlist) singleType() types.Type {
+ if nl := xl.norm(); len(nl) == 1 {
+ return nl[0].typ // if nl.isAll() then typ is nil, which is ok
+ }
+ return nil
+}
+
+// union returns the union xl βˆͺ yl.
+func (xl termlist) union(yl termlist) termlist {
+ return append(xl, yl...).norm()
+}
+
+// intersect returns the intersection xl ∩ yl.
+func (xl termlist) intersect(yl termlist) termlist {
+ if xl.isEmpty() || yl.isEmpty() {
+ return nil
+ }
+
+ // Quadratic algorithm, but good enough for now.
+ // TODO(gri) fix asymptotic performance
+ var rl termlist
+ for _, x := range xl {
+ for _, y := range yl {
+ if r := x.intersect(y); r != nil {
+ rl = append(rl, r)
+ }
+ }
+ }
+ return rl.norm()
+}
+
+// equal reports whether xl and yl represent the same type set.
+func (xl termlist) equal(yl termlist) bool {
+ // TODO(gri) this should be more efficient
+ return xl.subsetOf(yl) && yl.subsetOf(xl)
+}
+
+// includes reports whether t ∈ xl.
+func (xl termlist) includes(t types.Type) bool {
+ for _, x := range xl {
+ if x.includes(t) {
+ return true
+ }
+ }
+ return false
+}
+
+// supersetOf reports whether y βŠ† xl.
+func (xl termlist) supersetOf(y *term) bool {
+ for _, x := range xl {
+ if y.subsetOf(x) {
+ return true
+ }
+ }
+ return false
+}
+
+// subsetOf reports whether xl βŠ† yl.
+func (xl termlist) subsetOf(yl termlist) bool {
+ if yl.isEmpty() {
+ return xl.isEmpty()
+ }
+
+ // each term x of xl must be a subset of yl
+ for _, x := range xl {
+ if !yl.supersetOf(x) {
+ return false // x is not a subset yl
+ }
+ }
+ return true
+}
diff --git a/typeparams/typeparams_go117.go b/typeparams/typeparams_go117.go
new file mode 100644
index 0000000..c1da793
--- /dev/null
+++ b/typeparams/typeparams_go117.go
@@ -0,0 +1,201 @@
+// Copyright 2021 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.
+
+//go:build !go1.18
+
+package typeparams
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+)
+
+const enabled = false
+
+func unsupported() {
+ panic("type parameters are unsupported at this go version")
+}
+
+// IndexListExpr is a placeholder type, as type parameters are not supported at
+// this Go version. Its methods panic on use.
+type IndexListExpr struct {
+ ast.Expr
+ X ast.Expr // expression
+ Lbrack token.Pos // position of "["
+ Indices []ast.Expr // index expressions
+ Rbrack token.Pos // position of "]"
+}
+
+func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos }
+func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos }
+
+// ForTypeSpec returns an empty field list, as type parameters on not supported
+// at this Go version.
+func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
+ return nil
+}
+
+// ForFuncType returns an empty field list, as type parameters are not
+// supported at this Go version.
+func ForFuncType(*ast.FuncType) *ast.FieldList {
+ return nil
+}
+
+// TypeParam is a placeholder type, as type parameters are not supported at
+// this Go version. Its methods panic on use.
+type TypeParam struct{ types.Type }
+
+func (*TypeParam) String() string { unsupported(); return "" }
+func (*TypeParam) Underlying() types.Type { unsupported(); return nil }
+func (*TypeParam) Index() int { unsupported(); return 0 }
+func (*TypeParam) Constraint() types.Type { unsupported(); return nil }
+func (*TypeParam) SetConstraint(types.Type) { unsupported() }
+func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil }
+
+// TypeParamList is a placeholder for an empty type parameter list.
+type TypeParamList struct{}
+
+func (*TypeParamList) Len() int { return 0 }
+func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil }
+
+// TypeList is a placeholder for an empty type list.
+type TypeList struct{}
+
+func (*TypeList) Len() int { return 0 }
+func (*TypeList) At(int) types.Type { unsupported(); return nil }
+
+// NewTypeParam is unsupported at this Go version, and panics.
+func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
+ unsupported()
+ return nil
+}
+
+// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or
+// typeParams is non-empty.
+func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
+ if len(recvTypeParams) != 0 || len(typeParams) != 0 {
+ unsupported()
+ }
+ return types.NewSignature(recv, params, results, variadic)
+}
+
+// ForSignature returns an empty slice.
+func ForSignature(*types.Signature) *TypeParamList {
+ return nil
+}
+
+// RecvTypeParams returns a nil slice.
+func RecvTypeParams(sig *types.Signature) *TypeParamList {
+ return nil
+}
+
+// IsComparable returns false, as no interfaces are type-restricted at this Go
+// version.
+func IsComparable(*types.Interface) bool {
+ return false
+}
+
+// IsMethodSet returns true, as no interfaces are type-restricted at this Go
+// version.
+func IsMethodSet(*types.Interface) bool {
+ return true
+}
+
+// IsImplicit returns false, as no interfaces are implicit at this Go version.
+func IsImplicit(*types.Interface) bool {
+ return false
+}
+
+// MarkImplicit does nothing, because this Go version does not have implicit
+// interfaces.
+func MarkImplicit(*types.Interface) {}
+
+// ForNamed returns an empty type parameter list, as type parameters are not
+// supported at this Go version.
+func ForNamed(*types.Named) *TypeParamList {
+ return nil
+}
+
+// SetForNamed panics if tparams is non-empty.
+func SetForNamed(_ *types.Named, tparams []*TypeParam) {
+ if len(tparams) > 0 {
+ unsupported()
+ }
+}
+
+// NamedTypeArgs returns nil.
+func NamedTypeArgs(*types.Named) *TypeList {
+ return nil
+}
+
+// NamedTypeOrigin is the identity method at this Go version.
+func NamedTypeOrigin(named *types.Named) types.Type {
+ return named
+}
+
+// Term holds information about a structural type restriction.
+type Term struct {
+ tilde bool
+ typ types.Type
+}
+
+func (m *Term) Tilde() bool { return m.tilde }
+func (m *Term) Type() types.Type { return m.typ }
+func (m *Term) String() string {
+ pre := ""
+ if m.tilde {
+ pre = "~"
+ }
+ return pre + m.typ.String()
+}
+
+// NewTerm creates a new placeholder term type.
+func NewTerm(tilde bool, typ types.Type) *Term {
+ return &Term{tilde, typ}
+}
+
+// Union is a placeholder type, as type parameters are not supported at this Go
+// version. Its methods panic on use.
+type Union struct{ types.Type }
+
+func (*Union) String() string { unsupported(); return "" }
+func (*Union) Underlying() types.Type { unsupported(); return nil }
+func (*Union) Len() int { return 0 }
+func (*Union) Term(i int) *Term { unsupported(); return nil }
+
+// NewUnion is unsupported at this Go version, and panics.
+func NewUnion(terms []*Term) *Union {
+ unsupported()
+ return nil
+}
+
+// InitInstances is a noop at this Go version.
+func InitInstances(*types.Info) {}
+
+// Instance is a placeholder type, as type parameters are not supported at this
+// Go version.
+type Instance struct {
+ TypeArgs *TypeList
+ Type types.Type
+}
+
+// GetInstances returns a nil map, as type parameters are not supported at this
+// Go version.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
+
+// Context is a placeholder type, as type parameters are not supported at
+// this Go version.
+type Context struct{}
+
+// NewContext returns a placeholder Context instance.
+func NewContext() *Context {
+ return &Context{}
+}
+
+// Instantiate is unsupported on this Go version, and panics.
+func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
+ unsupported()
+ return nil, nil
+}
diff --git a/typeparams/typeparams_go118.go b/typeparams/typeparams_go118.go
new file mode 100644
index 0000000..0b35449
--- /dev/null
+++ b/typeparams/typeparams_go118.go
@@ -0,0 +1,147 @@
+// Copyright 2021 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.
+
+//go:build go1.18
+
+package typeparams
+
+import (
+ "go/ast"
+ "go/types"
+)
+
+const enabled = true
+
+// IndexListExpr is an alias for ast.IndexListExpr.
+type IndexListExpr = ast.IndexListExpr
+
+// ForTypeSpec returns n.TypeParams.
+func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
+ if n == nil {
+ return nil
+ }
+ return n.TypeParams
+}
+
+// ForFuncType returns n.TypeParams.
+func ForFuncType(n *ast.FuncType) *ast.FieldList {
+ if n == nil {
+ return nil
+ }
+ return n.TypeParams
+}
+
+// TypeParam is an alias for types.TypeParam
+type TypeParam = types.TypeParam
+
+// TypeParamList is an alias for types.TypeParamList
+type TypeParamList = types.TypeParamList
+
+// TypeList is an alias for types.TypeList
+type TypeList = types.TypeList
+
+// NewTypeParam calls types.NewTypeParam.
+func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
+ return types.NewTypeParam(name, constraint)
+}
+
+// NewSignatureType calls types.NewSignatureType.
+func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
+ return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic)
+}
+
+// ForSignature returns sig.TypeParams()
+func ForSignature(sig *types.Signature) *TypeParamList {
+ return sig.TypeParams()
+}
+
+// RecvTypeParams returns sig.RecvTypeParams().
+func RecvTypeParams(sig *types.Signature) *TypeParamList {
+ return sig.RecvTypeParams()
+}
+
+// IsComparable calls iface.IsComparable().
+func IsComparable(iface *types.Interface) bool {
+ return iface.IsComparable()
+}
+
+// IsMethodSet calls iface.IsMethodSet().
+func IsMethodSet(iface *types.Interface) bool {
+ return iface.IsMethodSet()
+}
+
+// IsImplicit calls iface.IsImplicit().
+func IsImplicit(iface *types.Interface) bool {
+ return iface.IsImplicit()
+}
+
+// MarkImplicit calls iface.MarkImplicit().
+func MarkImplicit(iface *types.Interface) {
+ iface.MarkImplicit()
+}
+
+// ForNamed extracts the (possibly empty) type parameter object list from
+// named.
+func ForNamed(named *types.Named) *TypeParamList {
+ return named.TypeParams()
+}
+
+// SetForNamed sets the type params tparams on n. Each tparam must be of
+// dynamic type *types.TypeParam.
+func SetForNamed(n *types.Named, tparams []*TypeParam) {
+ n.SetTypeParams(tparams)
+}
+
+// NamedTypeArgs returns named.TypeArgs().
+func NamedTypeArgs(named *types.Named) *TypeList {
+ return named.TypeArgs()
+}
+
+// NamedTypeOrigin returns named.Orig().
+func NamedTypeOrigin(named *types.Named) types.Type {
+ return named.Origin()
+}
+
+// Term is an alias for types.Term.
+type Term = types.Term
+
+// NewTerm calls types.NewTerm.
+func NewTerm(tilde bool, typ types.Type) *Term {
+ return types.NewTerm(tilde, typ)
+}
+
+// Union is an alias for types.Union
+type Union = types.Union
+
+// NewUnion calls types.NewUnion.
+func NewUnion(terms []*Term) *Union {
+ return types.NewUnion(terms)
+}
+
+// InitInstances initializes info to record information about type and function
+// instances.
+func InitInstances(info *types.Info) {
+ info.Instances = make(map[*ast.Ident]types.Instance)
+}
+
+// Instance is an alias for types.Instance.
+type Instance = types.Instance
+
+// GetInstances returns info.Instances.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance {
+ return info.Instances
+}
+
+// Context is an alias for types.Context.
+type Context = types.Context
+
+// NewContext calls types.NewContext.
+func NewContext() *Context {
+ return types.NewContext()
+}
+
+// Instantiate calls types.Instantiate.
+func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
+ return types.Instantiate(ctxt, typ, targs, validate)
+}
diff --git a/typeparams/typeparams_test.go b/typeparams/typeparams_test.go
new file mode 100644
index 0000000..2136a59
--- /dev/null
+++ b/typeparams/typeparams_test.go
@@ -0,0 +1,137 @@
+// Copyright 2021 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.
+
+//go:build go1.18
+
+package typeparams_test
+
+import (
+ "bytes"
+ "go/ast"
+ "go/build"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "testing"
+)
+
+// TestAPIConsistency verifies that exported APIs match at Go 1.17 and Go
+// 1.18+.
+//
+// It relies on the convention that the names of type aliases in the typeparams
+// package match the names of the types they are aliasing.
+//
+// This test could be made more precise.
+func TestAPIConsistency(t *testing.T) {
+ api118 := getAPI(buildPackage(t, true))
+ api117 := getAPI(buildPackage(t, false))
+
+ for name, api := range api117 {
+ if api != api118[name] {
+ t.Errorf("%q: got %s at 1.17, but %s at 1.18+", name, api, api118[name])
+ }
+ delete(api118, name)
+ }
+ for name, api := range api118 {
+ if api != api117[name] {
+ t.Errorf("%q: got %s at 1.18+, but %s at 1.17", name, api, api117[name])
+ }
+ }
+}
+
+func getAPI(pkg *types.Package) map[string]string {
+ api := make(map[string]string)
+ for _, name := range pkg.Scope().Names() {
+ if !token.IsExported(name) {
+ continue
+ }
+ api[name] = name
+ obj := pkg.Scope().Lookup(name)
+ if f, ok := obj.(*types.Func); ok {
+ api[name] = formatSignature(f.Type().(*types.Signature))
+ }
+ typ := pkg.Scope().Lookup(name).Type()
+ // Consider method sets of pointer and non-pointer receivers.
+ msets := map[string]*types.MethodSet{
+ name: types.NewMethodSet(typ),
+ "*" + name: types.NewMethodSet(types.NewPointer(typ)),
+ }
+ for name, mset := range msets {
+ for i := 0; i < mset.Len(); i++ {
+ f := mset.At(i).Obj().(*types.Func)
+ mname := f.Name()
+ if token.IsExported(mname) {
+ api[name+"."+mname] = formatSignature(f.Type().(*types.Signature))
+ }
+ }
+ }
+ }
+ return api
+}
+
+func formatSignature(sig *types.Signature) string {
+ var b bytes.Buffer
+ b.WriteString("func")
+ writeTuple(&b, sig.Params())
+ writeTuple(&b, sig.Results())
+ return b.String()
+}
+
+func writeTuple(buf *bytes.Buffer, t *types.Tuple) {
+ buf.WriteRune('(')
+
+ // The API at Go 1.18 uses aliases for types in go/types. These types are
+ // _actually_ in the go/types package, and therefore would be formatted as
+ // e.g. *types.TypeParam, which would not match *typeparams.TypeParam --
+ // go/types does not track aliases. As we use the same name for all aliases,
+ // we can make the formatted signatures match by dropping the package
+ // qualifier.
+ qf := func(*types.Package) string { return "" }
+
+ for i := 0; i < t.Len(); i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(types.TypeString(t.At(i).Type(), qf))
+ }
+ buf.WriteRune(')')
+}
+
+func buildPackage(t *testing.T, go118 bool) *types.Package {
+ ctxt := build.Default
+ if !go118 {
+ for i, tag := range ctxt.ReleaseTags {
+ if tag == "go1.18" {
+ ctxt.ReleaseTags = ctxt.ReleaseTags[:i]
+ break
+ }
+ }
+ }
+ bpkg, err := ctxt.ImportDir(".", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return typeCheck(t, bpkg.GoFiles)
+}
+
+func typeCheck(t *testing.T, filenames []string) *types.Package {
+ fset := token.NewFileSet()
+ var files []*ast.File
+ for _, name := range filenames {
+ f, err := parser.ParseFile(fset, name, nil, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files = append(files, f)
+ }
+ conf := types.Config{
+ Importer: importer.Default(),
+ }
+ pkg, err := conf.Check("", fset, files, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return pkg
+}
diff --git a/typeparams/typeterm.go b/typeparams/typeterm.go
new file mode 100644
index 0000000..7350bb7
--- /dev/null
+++ b/typeparams/typeterm.go
@@ -0,0 +1,169 @@
+// Copyright 2021 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.
+
+// Code generated by copytermlist.go DO NOT EDIT.
+
+package typeparams
+
+import "go/types"
+
+// A term describes elementary type sets:
+//
+// βˆ…: (*term)(nil) == βˆ… // set of no types (empty set)
+// 𝓀: &term{} == 𝓀 // set of all types (𝓀niverse)
+// T: &term{false, T} == {T} // set of type T
+// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t
+type term struct {
+ tilde bool // valid if typ != nil
+ typ types.Type
+}
+
+func (x *term) String() string {
+ switch {
+ case x == nil:
+ return "βˆ…"
+ case x.typ == nil:
+ return "𝓀"
+ case x.tilde:
+ return "~" + x.typ.String()
+ default:
+ return x.typ.String()
+ }
+}
+
+// equal reports whether x and y represent the same type set.
+func (x *term) equal(y *term) bool {
+ // easy cases
+ switch {
+ case x == nil || y == nil:
+ return x == y
+ case x.typ == nil || y.typ == nil:
+ return x.typ == y.typ
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ return x.tilde == y.tilde && types.Identical(x.typ, y.typ)
+}
+
+// union returns the union x βˆͺ y: zero, one, or two non-nil terms.
+func (x *term) union(y *term) (_, _ *term) {
+ // easy cases
+ switch {
+ case x == nil && y == nil:
+ return nil, nil // βˆ… βˆͺ βˆ… == βˆ…
+ case x == nil:
+ return y, nil // βˆ… βˆͺ y == y
+ case y == nil:
+ return x, nil // x βˆͺ βˆ… == x
+ case x.typ == nil:
+ return x, nil // 𝓀 βˆͺ y == 𝓀
+ case y.typ == nil:
+ return y, nil // x βˆͺ 𝓀 == 𝓀
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return x, y // x βˆͺ y == (x, y) if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t βˆͺ ~t == ~t
+ // ~t βˆͺ T == ~t
+ // T βˆͺ ~t == ~t
+ // T βˆͺ T == T
+ if x.tilde || !y.tilde {
+ return x, nil
+ }
+ return y, nil
+}
+
+// intersect returns the intersection x ∩ y.
+func (x *term) intersect(y *term) *term {
+ // easy cases
+ switch {
+ case x == nil || y == nil:
+ return nil // βˆ… ∩ y == βˆ… and ∩ βˆ… == βˆ…
+ case x.typ == nil:
+ return y // 𝓀 ∩ y == y
+ case y.typ == nil:
+ return x // x ∩ 𝓀 == x
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return nil // x ∩ y == βˆ… if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t ∩ ~t == ~t
+ // ~t ∩ T == T
+ // T ∩ ~t == T
+ // T ∩ T == T
+ if !x.tilde || y.tilde {
+ return x
+ }
+ return y
+}
+
+// includes reports whether t ∈ x.
+func (x *term) includes(t types.Type) bool {
+ // easy cases
+ switch {
+ case x == nil:
+ return false // t ∈ βˆ… == false
+ case x.typ == nil:
+ return true // t ∈ 𝓀 == true
+ }
+ // βˆ… βŠ‚ x βŠ‚ 𝓀
+
+ u := t
+ if x.tilde {
+ u = under(u)
+ }
+ return types.Identical(x.typ, u)
+}
+
+// subsetOf reports whether x βŠ† y.
+func (x *term) subsetOf(y *term) bool {
+ // easy cases
+ switch {
+ case x == nil:
+ return true // βˆ… βŠ† y == true
+ case y == nil:
+ return false // x βŠ† βˆ… == false since x != βˆ…
+ case y.typ == nil:
+ return true // x βŠ† 𝓀 == true
+ case x.typ == nil:
+ return false // 𝓀 βŠ† y == false since y != 𝓀
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return false // x βŠ† y == false if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t βŠ† ~t == true
+ // ~t βŠ† T == false
+ // T βŠ† ~t == true
+ // T βŠ† T == true
+ return !x.tilde || y.tilde
+}
+
+// disjoint reports whether x ∩ y == βˆ….
+// x.typ and y.typ must not be nil.
+func (x *term) disjoint(y *term) bool {
+ if debug && (x.typ == nil || y.typ == nil) {
+ panic("invalid argument(s)")
+ }
+ ux := x.typ
+ if y.tilde {
+ ux = under(ux)
+ }
+ uy := y.typ
+ if x.tilde {
+ uy = under(uy)
+ }
+ return !types.Identical(ux, uy)
+}