diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 17:35:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 17:35:35 +0000 |
commit | 331441e10889478d5a88eeda5d74057904e5e384 (patch) | |
tree | 0e63feed70b3e3fa3bf3096bb7985d4a80425cb3 /typeparams | |
parent | Initial commit. (diff) | |
download | golang-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.go | 182 | ||||
-rw-r--r-- | typeparams/common_test.go | 238 | ||||
-rw-r--r-- | typeparams/copytermlist.go | 99 | ||||
-rw-r--r-- | typeparams/example/README.md | 898 | ||||
-rw-r--r-- | typeparams/example/findtypeparams/main.go | 156 | ||||
-rw-r--r-- | typeparams/example/generic-go-types.md | 448 | ||||
-rw-r--r-- | typeparams/example/genericmethods/main.go | 122 | ||||
-rw-r--r-- | typeparams/example/implicit/main.go | 52 | ||||
-rw-r--r-- | typeparams/example/instantiation/main.go | 158 | ||||
-rw-r--r-- | typeparams/example/interfaces/main.go | 138 | ||||
-rw-r--r-- | typeparams/example/methoddecls/main.go | 137 | ||||
-rw-r--r-- | typeparams/example/predicates/main.go | 114 | ||||
-rw-r--r-- | typeparams/example/typesets/main.go | 71 | ||||
-rw-r--r-- | typeparams/go.mod | 3 | ||||
-rw-r--r-- | typeparams/normalize.go | 200 | ||||
-rw-r--r-- | typeparams/normalize_test.go | 104 | ||||
-rw-r--r-- | typeparams/termlist.go | 172 | ||||
-rw-r--r-- | typeparams/typeparams_go117.go | 201 | ||||
-rw-r--r-- | typeparams/typeparams_go118.go | 147 | ||||
-rw-r--r-- | typeparams/typeparams_test.go | 137 | ||||
-rw-r--r-- | typeparams/typeterm.go | 169 |
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) +} |